fix log dump race and unclosed HDF5 data pool file in DamarisGUI

This commit is contained in:
markusro committed 2026-10-18 11:11:14 +02:00
1 parent efb01250c8
commit da1dc4a088
1 file changed
+116 -114
+116 -114
View File
@@ -613,8 +613,9 @@ class DamarisGUI:
logger.debug("Data cleanup took %.3fs", time.time() - data_cleanup_start)
# set the text mark for hdf logging
if self.log.textbuffer.get_mark( "lastdumped" ) is None:
self.log.textbuffer.create_mark( "lastdumped", self.log.textbuffer.get_end_iter( ), left_gravity=True )
with self.log.lock:
if self.log.textbuffer.get_mark( "lastdumped" ) is None:
self.log.textbuffer.create_mark( "lastdumped", self.log.textbuffer.get_end_iter( ), left_gravity=True )
loop_run=0
#user shorter interval because this blocks user events from the GUI
@@ -991,125 +992,124 @@ class DamarisGUI:
# now it's time to create the hdf file
dump_file = None
if not os.path.isfile( self.dump_filename ):
if not init:
logger.warning("Dump file \"%s\" vanished unexpectedly, creating new one", self.dump_filename)
# have a look to the path and create necessary directories
dir_stack = [ ]
dir_trunk = os.path.dirname( os.path.abspath( self.dump_filename ) )
while dir_trunk != "" and not os.path.isdir( dir_trunk ):
dir_stack.append( os.path.basename( dir_trunk ) )
dir_trunk = os.path.dirname( dir_trunk )
try:
while len( dir_stack ):
dir_trunk = os.path.join( dir_trunk, dir_stack.pop( ) )
if os.path.isdir( dir_trunk ):
continue
os.mkdir( dir_trunk )
except OSError as e:
logger.error("Could not create dump directory '%s', so hdf5 dumps disabled: %s", dir_trunk, e)
self.dump_filename = ""
self.dump_timeinterval = 0
return True
try:
if not os.path.isfile( self.dump_filename ):
if not init:
logger.warning("Dump file \"%s\" vanished unexpectedly, creating new one", self.dump_filename)
# have a look to the path and create necessary directories
dir_stack = [ ]
dir_trunk = os.path.dirname( os.path.abspath( self.dump_filename ) )
while dir_trunk != "" and not os.path.isdir( dir_trunk ):
dir_stack.append( os.path.basename( dir_trunk ) )
dir_trunk = os.path.dirname( dir_trunk )
try:
while len( dir_stack ):
dir_trunk = os.path.join( dir_trunk, dir_stack.pop( ) )
if os.path.isdir( dir_trunk ):
continue
os.mkdir( dir_trunk )
except OSError as e:
logger.error("Could not create dump directory '%s', so hdf5 dumps disabled: %s", dir_trunk, e)
self.dump_filename = ""
self.dump_timeinterval = 0
return True
# create new dump file
dump_file = tables.open_file( self.dump_filename, mode="w", title="DAMARIS experiment data" )
# write scripts and other useful information
scriptgroup = dump_file.create_group( "/", "scripts", "Used Scripts" )
exp_text, res_text = self.sw.get_scripts( )
if self.si.exp_script:
dump_file.create_array( scriptgroup, "experiment_script", exp_text.encode('utf-8') )
if self.si.res_script:
dump_file.create_array( scriptgroup, "result_script", res_text.encode('utf-8') )
if self.si.backend_executable:
dump_file.create_array( scriptgroup, "backend_executable", self.si.backend_executable.encode('utf-8') )
if self.spool_dir:
dump_file.create_array( scriptgroup, "spool_directory", self.spool_dir.encode('utf-8') )
timeline_tablecols = numpy.recarray( 0, dtype=([ ("time", "S17"),
("experiments", "int64"),
("results", "int64") ]) )
timeline_table = dump_file.create_table( "/", "timeline", timeline_tablecols,
title="Timeline of Experiment" )
if tables.__version__[ 0 ] == "1":
logarray = dump_file.create_vlarray( where=dump_file.root,
name="log",
atom=tables.StringAtom( length=120 ),
title="log messages",
filters=tables.Filters( complevel=9, complib='zlib' ) )
else:
logarray = dump_file.create_earray( where=dump_file.root,
name="log",
atom=tables.StringAtom( itemsize=120 ),
shape=(0,),
title="log messages",
filters=tables.Filters( complevel=9, complib='zlib' ) )
# create new dump file
dump_file = tables.open_file( self.dump_filename, mode="w", title="DAMARIS experiment data" )
# write scripts and other useful information
scriptgroup = dump_file.create_group( "/", "scripts", "Used Scripts" )
exp_text, res_text = self.sw.get_scripts( )
if self.si.exp_script:
dump_file.create_array( scriptgroup, "experiment_script", exp_text.encode('utf-8') )
if self.si.res_script:
dump_file.create_array( scriptgroup, "result_script", res_text.encode('utf-8') )
if self.si.backend_executable:
dump_file.create_array( scriptgroup, "backend_executable", self.si.backend_executable.encode('utf-8') )
if self.spool_dir:
dump_file.create_array( scriptgroup, "spool_directory", self.spool_dir.encode('utf-8') )
timeline_tablecols = numpy.recarray( 0, dtype=([ ("time", "S17"),
("experiments", "int64"),
("results", "int64") ]) )
timeline_table = dump_file.create_table( "/", "timeline", timeline_tablecols,
title="Timeline of Experiment" )
if tables.__version__[ 0 ] == "1":
logarray = dump_file.create_vlarray( where=dump_file.root,
name="log",
atom=tables.StringAtom( length=120 ),
title="log messages",
filters=tables.Filters( complevel=9, complib='zlib' ) )
else:
logarray = dump_file.create_earray( where=dump_file.root,
name="log",
atom=tables.StringAtom( itemsize=120 ),
shape=(0,),
title="log messages",
filters=tables.Filters( complevel=9, complib='zlib' ) )
if dump_file is None and os.path.isfile( self.dump_filename ) and tables.is_pytables_file( self.dump_filename ):
# prepare for update
dump_file = tables.open_file( self.dump_filename, mode="r+" )
if dump_file is None and os.path.isfile( self.dump_filename ) and tables.is_pytables_file( self.dump_filename ):
# prepare for update
dump_file = tables.open_file( self.dump_filename, mode="r+" )
if dump_file is None:
# exit!
logger.error("Could not create dump directory '%s', so hdf5 dumps disabled", dir_trunk)
self.dump_filename = ""
self.dump_timeinterval = 0
return True
if dump_file is None:
# exit!
logger.error("Could not create dump directory '%s', so hdf5 dumps disabled", dir_trunk)
self.dump_filename = ""
self.dump_timeinterval = 0
return True
# no undo please!
if dump_file.is_undo_enabled( ):
dump_file.disable_undo( )
# no undo please!
if dump_file.is_undo_enabled( ):
dump_file.disable_undo( )
# save the data!
if self.data is not None:
self.data.write_hdf5( dump_file, where="/", name="data_pool",
complib=self.dump_complib, complevel=self.dump_complevel,
incremental=not init, write_interval=self.dump_timeinterval )
# save the data!
if self.data is not None:
self.data.write_hdf5( dump_file, where="/", name="data_pool",
complib=self.dump_complib, complevel=self.dump_complevel,
incremental=not init, write_interval=self.dump_timeinterval )
# Re-enable run button if this was called from save thread
# NO: this is dangerous as it allows starting a new experiment before cleanup is finished.
# it will be re-enabled in observe_running_experiment after joining this thread.
# if not init and threading.current_thread().name == "dump states":
# gdk.threads_enter()
# self.toolbar_run_button.set_sensitive(True)
# gdk.threads_leave()
# Re-enable run button if this was called from save thread
# NO: this is dangerous as it allows starting a new experiment before cleanup is finished.
# it will be re-enabled in observe_running_experiment after joining this thread.
# if not init and threading.current_thread().name == "dump states":
# gdk.threads_enter()
# self.toolbar_run_button.set_sensitive(True)
# gdk.threads_leave()
# now save additional information
e = 0
r = 0
if self.si is not None:
e = self.si.data.get( "__recentexperiment", -1 ) + 1
r = self.si.data.get( "__recentresult", -1 ) + 1
timeline_table = dump_file.root.timeline
timeline_row = timeline_table.row
timeline_row[ "time" ] = time.strftime( "%Y%m%d %H:%M:%S" )
timeline_row[ "experiments" ] = e
timeline_row[ "results" ] = r
timeline_row.append( )
timeline_table.flush( )
# now save additional information
e = 0
r = 0
if self.si is not None:
e = self.si.data.get( "__recentexperiment", -1 ) + 1
r = self.si.data.get( "__recentresult", -1 ) + 1
timeline_table = dump_file.root.timeline
timeline_row = timeline_table.row
timeline_row[ "time" ] = time.strftime( "%Y%m%d %H:%M:%S" )
timeline_row[ "experiments" ] = e
timeline_row[ "results" ] = r
timeline_row.append( )
timeline_table.flush( )
# save log window information:
# also save backend's logfile information?
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( )
logtextbuffer.move_mark( last_end, logtext_end )
# Create new iterators after the mark move
logtext_start = logtextbuffer.get_iter_at_mark( last_end )
logtext_end = logtextbuffer.get_end_iter( )
# recode from unicode
logtext = logtextbuffer.get_text( logtext_start, logtext_end, True )
# avoid circular references (seems to be necessary with gtk-2.12)
# del logtextbuffer, logtext_start, logtext_end, last_end
for l in logtext.splitlines( ):
dump_file.root.log.append( numpy.array( [ l ], dtype="S120" ) )
# 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 )
if logtext is not None:
for l in logtext.splitlines( ):
dump_file.root.log.append( numpy.array( [ l ], dtype="S120" ) )
dump_file.flush( )
dump_file.close( )
dump_file.flush( )
finally:
if dump_file is not None:
dump_file.close( )
self.last_dumped = time.time( )
del dump_file
return True
@@ -1351,6 +1351,7 @@ class LogWindow:
self.textview.connect( "key-press-event", self.textview_keypress)
apply_font_css(self.textview, "Monospace 10")
self.textbuffer = self.textview.get_buffer( )
self.lock = threading.Lock( )
self.last_timetag = None
self( "Started in directory %s\n" % os.getcwd( ) )
@@ -1365,9 +1366,10 @@ class LogWindow:
timetag_int = math.floor( timetag )
timetag_ms = int( (timetag - timetag_int) * 1000 )
date_tag = time.strftime( "%Y%m%d %H:%M:%S.%%03d:\n", time.localtime( timetag_int ) ) % timetag_ms
self.textbuffer.place_cursor( self.textbuffer.get_end_iter( ) )
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)
with self.lock:
self.textbuffer.place_cursor( self.textbuffer.get_end_iter( ) )
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 textview_keypress( self, widget, event, data=None ):
if event.state & gdk.ModifierType.CONTROL_MASK != 0: