From 0ca78cc5669542230446c57d19fecd3bc0c5feb3 Mon Sep 17 00:00:00 2001 From: Markus Rosenstihl Date: Sun, 18 Oct 2026 11:27:16 +0200 Subject: [PATCH] snapshot log text on GTK main thread before HDF5 dump --- src/damaris/gui/DamarisGUI.py | 48 +++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/damaris/gui/DamarisGUI.py b/src/damaris/gui/DamarisGUI.py index ee495a0..2a18551 100644 --- a/src/damaris/gui/DamarisGUI.py +++ b/src/damaris/gui/DamarisGUI.py @@ -738,7 +738,7 @@ class DamarisGUI: self.save_thread = None self.dump_filename = "" if actual_config[ "data_pool_name" ] != "": - self.dump_states( init=True ) + self.dump_states( init=True, logtext=self.log.snapshot_since_last_dump( ) ) gobject.timeout_add( 200, self.observe_running_experiment ) @@ -862,8 +862,10 @@ class DamarisGUI: # Disable run button during data saving to prevent UI confusion self.toolbar_run_button.set_sensitive(False) - # thread to save data... - self.save_thread = threading.Thread( target=self.dump_states, name="dump states" ) + # thread to save data... snapshot log on the main thread first + self.save_thread = threading.Thread( target=self.dump_states, + args=(False, self.log.snapshot_since_last_dump( )), + name="dump states" ) self.save_thread.start( ) self.dump_start_time = time.time( ) self.dump_dots = 0 @@ -909,16 +911,19 @@ class DamarisGUI: logger.info("Dumping data pool") self.dump_start_time = time.time( ) self.dump_dots = 0 - # thread to save data... - self.dump_thread = threading.Thread( target=self.dump_states, name="dump states" ) + # thread to save data... snapshot log on the main thread first + self.dump_thread = threading.Thread( target=self.dump_states, + args=(False, self.log.snapshot_since_last_dump( )), + name="dump states" ) self.dump_thread.start( ) # or look at them again return True - def dump_states( self, init=False ): + def dump_states( self, init=False, logtext=None ): """ init: constructs basic structure of this file + logtext: pending log window text, captured on the GTK main thread compress: optional argument for zlib compression 0-9 """ @@ -1091,16 +1096,7 @@ class DamarisGUI: # save log window information: # also save backend's logfile information? - # the dump thread must not race with the GUI thread appending messages - with self.log.lock: - logtextbuffer = self.log.textbuffer - last_end = logtextbuffer.get_mark( "lastdumped" ) - if last_end is None: - last_end = logtextbuffer.create_mark( "lastdumped", logtextbuffer.get_start_iter( ), left_gravity=True ) - logtext_start = logtextbuffer.get_iter_at_mark( last_end ) - logtext_end = logtextbuffer.get_end_iter( ) - logtext = logtextbuffer.get_text( logtext_start, logtext_end, True ) - logtextbuffer.move_mark( "lastdumped", logtext_end ) + # logtext was snapshotted on the GTK main thread before spawning if logtext is not None: for l in logtext.splitlines( ): dump_file.root.log.append( numpy.array( [ l ], dtype="S120" ) ) @@ -1371,6 +1367,26 @@ class LogWindow: self.textbuffer.insert_at_cursor( date_tag + str( message ) ) self.textview.scroll_to_mark( self.textbuffer.get_insert( ), 0.1 , False, 0.5, 0.5) + def snapshot_since_last_dump( self ): + """Return log text appended since the last dump and advance the mark. + + Must be called from the GTK main thread. The result is plain Python + data that can safely be passed to the dump thread. + + Examples: + >>> logtext = self.log.snapshot_since_last_dump( ) + """ + with self.lock: + tb = self.textbuffer + mark = tb.get_mark( "lastdumped" ) + if mark is None: + mark = tb.create_mark( "lastdumped", tb.get_start_iter( ), left_gravity=True ) + start = tb.get_iter_at_mark( mark ) + end = tb.get_end_iter( ) + text = tb.get_text( start, end, True ) + tb.move_mark( "lastdumped", end ) + return text + def textview_keypress( self, widget, event, data=None ): if event.state & gdk.ModifierType.CONTROL_MASK != 0: if event.keyval == gdk.keyval_from_name("f"):