44import inspect
55import os
66from pathlib import Path
7- import janus
87import queue
98import sqlite_utils
109import sys
@@ -330,13 +329,16 @@ def track_event(event):
330329 else :
331330 # For non-blocking writes, spawn a background task to
332331 # dispatch events after the write thread completes
333- task_id , reply_queue = result
332+ task_id , reply_future = result
334333
335334 async def _dispatch_events_after_write ():
336- write_result = await reply_queue .async_q .get ()
337- if not isinstance (write_result , Exception ):
338- for event in pending_events :
339- await self .ds .track_event (event )
335+ try :
336+ await reply_future
337+ except Exception :
338+ # if the write failed, don't emit success events
339+ return
340+ for event in pending_events :
341+ await self .ds .track_event (event )
340342
341343 asyncio .ensure_future (_dispatch_events_after_write ())
342344 result = task_id
@@ -390,18 +392,15 @@ async def _send_to_write_thread(
390392 )
391393 self ._write_thread .start ()
392394 task_id = uuid .uuid5 (uuid .NAMESPACE_DNS , "datasette.io" )
393- reply_queue = janus .Queue ()
395+ loop = asyncio .get_running_loop ()
396+ reply_future = loop .create_future ()
394397 self ._write_queue .put (
395- WriteTask (fn , task_id , reply_queue , isolated_connection , transaction )
398+ WriteTask (fn , task_id , loop , reply_future , isolated_connection , transaction )
396399 )
397400 if block :
398- result = await reply_queue .async_q .get ()
399- if isinstance (result , Exception ):
400- raise result
401- else :
402- return result
401+ return await reply_future
403402 else :
404- return task_id , reply_queue
403+ return task_id , reply_future
405404
406405 def _execute_writes (self ):
407406 # Infinite looping thread that protects the single write connection
@@ -422,36 +421,37 @@ def _execute_writes(self):
422421 except Exception :
423422 pass
424423 return
424+ exception = None
425+ result = None
425426 if conn_exception is not None :
426- result = conn_exception
427- else :
428- if task .isolated_connection :
429- isolated_connection = self .connect (write = True )
430- try :
431- result = task .fn (isolated_connection )
432- except Exception as e :
433- sys .stderr .write ("{}\n " .format (e ))
434- sys .stderr .flush ()
435- result = e
436- finally :
437- isolated_connection .close ()
438- try :
439- self ._all_file_connections .remove (isolated_connection )
440- except ValueError :
441- # Was probably a memory connection
442- pass
443- else :
427+ exception = conn_exception
428+ elif task .isolated_connection :
429+ isolated_connection = self .connect (write = True )
430+ try :
431+ result = task .fn (isolated_connection )
432+ except Exception as e :
433+ sys .stderr .write ("{}\n " .format (e ))
434+ sys .stderr .flush ()
435+ exception = e
436+ finally :
437+ isolated_connection .close ()
444438 try :
445- if task .transaction :
446- with conn :
447- result = task .fn (conn )
448- else :
439+ self ._all_file_connections .remove (isolated_connection )
440+ except ValueError :
441+ # Was probably a memory connection
442+ pass
443+ else :
444+ try :
445+ if task .transaction :
446+ with conn :
449447 result = task .fn (conn )
450- except Exception as e :
451- sys .stderr .write ("{}\n " .format (e ))
452- sys .stderr .flush ()
453- result = e
454- task .reply_queue .sync_q .put (result )
448+ else :
449+ result = task .fn (conn )
450+ except Exception as e :
451+ sys .stderr .write ("{}\n " .format (e ))
452+ sys .stderr .flush ()
453+ exception = e
454+ _deliver_write_result (task , result , exception )
455455
456456 async def execute_fn (self , fn ):
457457 self ._check_not_closed ()
@@ -892,16 +892,45 @@ def wrapped(conn):
892892
893893
894894class WriteTask :
895- __slots__ = ("fn" , "task_id" , "reply_queue" , "isolated_connection" , "transaction" )
895+ __slots__ = (
896+ "fn" ,
897+ "task_id" ,
898+ "loop" ,
899+ "reply_future" ,
900+ "isolated_connection" ,
901+ "transaction" ,
902+ )
896903
897- def __init__ (self , fn , task_id , reply_queue , isolated_connection , transaction ):
904+ def __init__ (
905+ self , fn , task_id , loop , reply_future , isolated_connection , transaction
906+ ):
898907 self .fn = fn
899908 self .task_id = task_id
900- self .reply_queue = reply_queue
909+ self .loop = loop
910+ self .reply_future = reply_future
901911 self .isolated_connection = isolated_connection
902912 self .transaction = transaction
903913
904914
915+ def _deliver_write_result (task , result , exception ):
916+ # Called from the write thread. Delivers the result back to the
917+ # awaiting coroutine on its event loop via call_soon_threadsafe.
918+ def _set ():
919+ if task .reply_future .done ():
920+ # Awaiter was cancelled; nothing to do.
921+ return
922+ if exception is not None :
923+ task .reply_future .set_exception (exception )
924+ else :
925+ task .reply_future .set_result (result )
926+
927+ try :
928+ task .loop .call_soon_threadsafe (_set )
929+ except RuntimeError :
930+ # Event loop has been closed; the awaiter is gone.
931+ pass
932+
933+
905934class QueryInterrupted (Exception ):
906935 def __init__ (self , e , sql , params ):
907936 self .e = e
0 commit comments