1818
1919async def acreate_connection (** config ) -> psycopg .AsyncConnection :
2020 "Create a new asyncio connection"
21- return await psycopg .AsyncConnection .connect (** config )
21+ connection = await psycopg .AsyncConnection .connect (** config )
22+ if not connection .autocommit :
23+ await connection .set_autocommit (True )
24+ return connection
2225
2326
2427def create_connection (** config ) -> psycopg .Connection :
25- return psycopg .Connection .connect (** config )
28+ connection = psycopg .Connection .connect (** config )
29+ if not connection .autocommit :
30+ connection .set_autocommit (True )
31+ return connection
2632
2733
2834class Broker :
35+ NOTIFY_QUERY_TEMPLATE = 'SELECT pg_notify(%s, %s);'
2936
3037 def __init__ (
3138 self ,
@@ -63,13 +70,18 @@ def __init__(
6370
6471 if config :
6572 self ._config : dict = config .copy ()
66- self ._config ['autocommit' ] = True
6773 else :
6874 self ._config = {}
6975
7076 self .channels = channels
7177 self .default_publish_channel = default_publish_channel
7278
79+ # If we are in the notification loop (receiving messages),
80+ # then we have to break out before sending messages
81+ # These variables track things so that we can exit, send, and re-enter
82+ self .notify_loop_active : bool = False
83+ self .notify_queue : list = []
84+
7385 def get_publish_channel (self , channel : Optional [str ] = None ) -> str :
7486 "Handle default for the publishing channel for calls to publish_message, shared sync and async"
7587 if channel is not None :
@@ -112,23 +124,35 @@ async def aprocess_notify(self, connected_callback: Optional[Callable] = None) -
112124
113125 while True :
114126 logger .debug ('Starting listening for pg_notify notifications' )
127+ self .notify_loop_active = True
115128 async for notify in connection .notifies ():
116129 yield notify .channel , notify .payload
130+ if self .notify_queue :
131+ break
132+ self .notify_loop_active = False
133+ for reply_to , reply_message in self .notify_queue :
134+ await self .apublish_message_from_cursor (cur , channel = reply_to , message = reply_message )
135+ self .notify_queue = []
136+
137+ async def apublish_message_from_cursor (self , cursor : psycopg .AsyncCursor , channel : Optional [str ] = None , message : str = '' ) -> None :
138+ """The inner logic of async message publishing where we already have a cursor"""
139+ await cursor .execute (self .NOTIFY_QUERY_TEMPLATE , (channel , message ))
117140
118141 async def apublish_message (self , channel : Optional [str ] = None , message : str = '' ) -> None : # public
119142 """asyncio way to publish a message, used to send control in control-and-reply
120143
121144 Not strictly necessary for the service itself if it sends replies in the workers,
122145 but this may change in the future.
123146 """
147+ if self .notify_loop_active :
148+ self .notify_queue .append ((channel , message ))
149+ return
150+
124151 connection = await self .aget_connection ()
125152 channel = self .get_publish_channel (channel )
126153
127154 async with connection .cursor () as cur :
128- if not message :
129- await cur .execute (f'NOTIFY { channel } ;' )
130- else :
131- await cur .execute (f"NOTIFY { channel } , '{ message } ';" )
155+ await self .apublish_message_from_cursor (cur , channel = channel , message = message )
132156
133157 logger .debug (f'Sent pg_notify message of { len (message )} chars to { channel } ' )
134158
@@ -159,10 +183,7 @@ def publish_message(self, channel: Optional[str] = None, message: str = '') -> N
159183 channel = self .get_publish_channel (channel )
160184
161185 with connection .cursor () as cur :
162- if message :
163- cur .execute ('SELECT pg_notify(%s, %s);' , (channel , message ))
164- else :
165- cur .execute (f'NOTIFY { channel } ;' )
186+ cur .execute (self .NOTIFY_QUERY_TEMPLATE , (channel , message ))
166187
167188 logger .debug (f'Sent pg_notify message of { len (message )} chars to { channel } ' )
168189
@@ -189,7 +210,6 @@ def connection_saver(**config) -> psycopg.Connection:
189210 Dispatcher does not manage connections, so this a simulation of that.
190211 """
191212 if connection_save ._connection is None :
192- config ['autocommit' ] = True
193213 connection_save ._connection = create_connection (** config )
194214 return connection_save ._connection
195215
@@ -202,6 +222,5 @@ async def async_connection_saver(**config) -> psycopg.AsyncConnection:
202222 Dispatcher does not manage connections, so this a simulation of that.
203223 """
204224 if connection_save ._async_connection is None :
205- config ['autocommit' ] = True
206225 connection_save ._async_connection = await acreate_connection (** config )
207226 return connection_save ._async_connection
0 commit comments