safer handling of calling account hooks, refined shutdown comment

This commit is contained in:
holger krekel
2022-05-04 11:27:57 +02:00
parent 81ee69010d
commit cd4d265055
3 changed files with 33 additions and 14 deletions

View File

@@ -698,7 +698,8 @@ class Account(object):
# mark the event thread for shutdown (latest on next incoming event) # mark the event thread for shutdown (latest on next incoming event)
self._event_thread.mark_shutdown() self._event_thread.mark_shutdown()
# stop_io also causes an info event that will terminate the event thread # stop_io also causes an info event which will wake up
# the EventThread's inner loop and let it notice the shutdown marker.
self.stop_io() self.stop_io()
self.log("wait for event thread to finish") self.log("wait for event thread to finish")

View File

@@ -1,5 +1,8 @@
import threading import threading
import sys
import traceback
import time import time
import io
import re import re
import os import os
from queue import Queue, Empty from queue import Queue, Empty
@@ -221,17 +224,13 @@ class EventThread(threading.Thread):
self._inner_run() self._inner_run()
def _inner_run(self): def _inner_run(self):
if self._marked_for_shutdown or self.account._dc_context is None:
return
event_emitter = ffi.gc( event_emitter = ffi.gc(
lib.dc_get_event_emitter(self.account._dc_context), lib.dc_get_event_emitter(self.account._dc_context),
lib.dc_event_emitter_unref, lib.dc_event_emitter_unref,
) )
while not self._marked_for_shutdown: while not self._marked_for_shutdown:
event = lib.dc_get_next_event(event_emitter) event = lib.dc_get_next_event(event_emitter)
if event == ffi.NULL: if event == ffi.NULL or self._marked_for_shutdown:
break
if self._marked_for_shutdown:
break break
evt = lib.dc_event_get_id(event) evt = lib.dc_event_get_id(event)
data1 = lib.dc_event_get_data1_int(event) data1 = lib.dc_event_get_data1_int(event)
@@ -245,15 +244,22 @@ class EventThread(threading.Thread):
lib.dc_event_unref(event) lib.dc_event_unref(event)
ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2)
try: self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event)
self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) for name, kwargs in self._map_ffi_event(ffi_event):
for name, kwargs in self._map_ffi_event(ffi_event): hook = getattr(self.account._pm.hook, name)
self.account.log("calling hook name={} kwargs={}".format(name, kwargs)) info = "call {} kwargs={} failed".format(name, kwargs)
hook = getattr(self.account._pm.hook, name) with self.swallow_and_log_exception(info):
hook(**kwargs) hook(**kwargs)
except Exception:
if not self._marked_for_shutdown and self.account._dc_context is not None: @contextmanager
raise def swallow_and_log_exception(self, info):
try:
yield
except Exception as ex:
logfile = io.StringIO()
traceback.print_exception(*sys.exc_info(), file=logfile)
self.account.log("{}\nException {}\nTraceback:\n{}"
.format(info, ex, logfile.getvalue()))
def _map_ffi_event(self, ffi_event: FFIEvent): def _map_ffi_event(self, ffi_event: FFIEvent):
name = ffi_event.name name = ffi_event.name

View File

@@ -140,3 +140,15 @@ def test_get_info_open(tmpdir):
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info assert 'deltachat_core_version' in info
assert 'database_dir' in info assert 'database_dir' in info
def test_logged_hook_failure(acfactory):
ac1 = acfactory.get_pseudo_configured_account()
cap = []
ac1.log = cap.append
with ac1._event_thread.swallow_and_log_exception("some"):
0/0
assert cap
assert "some" in str(cap)
assert "ZeroDivisionError" in str(cap)
assert "Traceback" in str(cap)