emacspy_threads.py 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import emacspy, socket, tempfile, queue, threading
  2. from emacspy import sym
  3. from typing import Optional
  4. import concurrent.futures, traceback
  5. _call_soon_queue: queue.Queue = queue.Queue(0)
  6. _wakeup_conn: Optional[socket.socket] = None
  7. _emacs_thread = threading.current_thread()
  8. def call_soon_in_main_thread(f):
  9. _call_soon_queue.put(f)
  10. if _wakeup_conn:
  11. _wakeup_conn.send(b'x')
  12. def run_in_main_thread_future(f):
  13. fut: concurrent.futures.Future = concurrent.futures.Future()
  14. def wrapper():
  15. try:
  16. fut.set_result(f())
  17. except Exception as exc:
  18. traceback.print_exc()
  19. fut.set_exception(exc)
  20. call_soon_in_main_thread(wrapper)
  21. return fut
  22. def run_in_main_thread(f):
  23. if _emacs_thread == threading.current_thread():
  24. raise Exception('already on emacs main thread')
  25. return run_in_main_thread_future(f).result()
  26. @emacspy.defun('emacspy-threads/wakeup')
  27. def wakeup(p, data):
  28. while True:
  29. try:
  30. f = _call_soon_queue.get_nowait()
  31. except queue.Empty:
  32. break
  33. f()
  34. def init():
  35. with tempfile.TemporaryDirectory() as dir:
  36. path = dir + '/socket'
  37. s = socket.socket(socket.AF_UNIX)
  38. s.bind(path)
  39. s.listen(1)
  40. # this is "self-pipe trick"
  41. emacspy.f.make_network_process(
  42. sym(":name"), "emacspy-wakeup",
  43. sym(":remote"), path,
  44. sym(":filter"), sym('emacspy-threads/wakeup'))
  45. global _wakeup_conn
  46. _wakeup_conn, _ = s.accept()
  47. wakeup(None, None)