How to completely remove a TFile from gROOT

Dear experts

I want to manage some TFile by a static map created myself, so I need to remove them from gROOT’s control. Here is what I do:

fFile=TFile::Open(filename.c_str(), mode.c_str());
fTree = new TTree(name, description);
fTree->SetDirectory(fFile);
fTree->SetAutoSave(100);
//Set branches here
gROOT->GetListOfFiles()->Remove(fFile);
//something

fFile->cd();
fTree->Write();
fFile->Close();

segfault occurs at Write(), where debug message are

Program received signal SIGSEGV, Segmentation fault.
0x00007fffbd874b5a in TClass::GetBaseClassOffset (isDerivedObject=true, address=0x0, toBase=0x1e269d0, this=0x35cc8c0)
    at /.../ROOT/root_v6.24.02/core/meta/src/TClass.cxx:2791
2791          return gCling->ClassInfo_GetBaseOffset(derived, base, address, isDerivedObject);
Missing separate debuginfos, use: dnf debuginfo-install ftgl-2.1.3-0.27.rc5.el9.x86_64 glib2-2.68.4-14.el9_4.1.x86_64 glibc-2.34-100.el9_4.3.x86_64 graphite2-1.3.14-9.el9.x86_64 harfbuzz-2.7.4-8.el9.x86_64 libGLEW-2.2.0-2.el9.x86_64 libXmu-1.1.3-8.el9.x86_64 libXt-1.2.0-6.el9.x86_64 libbrotli-1.0.9-6.el9.x86_64 libuuid-2.37.4-18.el9.x86_64 libxml2-2.9.13-4.el9.x86_64 lz4-libs-1.9.3-5.el9.x86_64 openssl-libs-3.2.2-6.el9_5.x86_64 sssd-client-2.9.5-4.el9_5.1.x86_64 xxhash-libs-0.8.2-1.el9.x86_64
(gdb) backtrace
#0  0x00007fffbd874b5a in TClass::GetBaseClassOffset (isDerivedObject=true, address=0x0, toBase=0x1e269d0,
    this=0x35cc8c0)
    at /.../ROOT/root_v6.24.02/core/meta/src/TClass.cxx:2791
#1  TClass::GetBaseClassOffset (this=this@entry=0x35cc8c0, toBase=toBase@entry=0x1e269d0, address=address@entry=0x0,
    isDerivedObject=isDerivedObject@entry=true)
    at /.../ROOT/root_v6.24.02/core/meta/src/TClass.cxx:2769
#2  0x00007fffbd2d835a in TBufferIO::WriteObjectAny (cacheReuse=<optimized out>, ptrClass=0x1e269d0, obj=0x329f790,
    this=0x46f7ca10)
    at /.../ROOT/root_v6.24.02/io/io/src/TBufferIO.cxx:518
#3  TBufferIO::WriteObjectAny (this=0x46f7ca10, obj=0x329f790, ptrClass=0x1e269d0, cacheReuse=<optimized out>)
    at /.../ROOT/root_v6.24.02/io/io/src/TBufferIO.cxx:492
#4  0x00007fffbd83efdd in operator<< <TObject> (obj=0x329f790, buf=...)
    at /.../ROOT/root_v6.24.02/core/base/inc/TBuffer.h:402
#5  TObjArray::Streamer (this=0x2ca3b18, b=...)
    at /.../ROOT/root_v6.24.02/core/cont/src/TObjArray.cxx:487
#6  0x00007fffbd2cbaf3 in TClass::Streamer (onfile_class=0x0, b=..., obj=0x2ca3b18, this=0x1e448f0)
    at /.../ROOT/root_v6.24.02/core/meta/inc/TClass.h:609
#7  TBufferFile::WriteFastArray (streamer=0x0, n=1, cl=0x1e448f0, start=<optimized out>, this=0x46f7ca10)
    at /.../ROOT/root_v6.24.02/io/io/src/TBufferFile.cxx:2255
#8  TBufferFile::WriteFastArray (this=0x46f7ca10, start=<optimized out>, cl=0x1e448f0, n=1, streamer=<optimized out>)
    at /.../ROOT/root_v6.24.02/io/io/src/TBufferFile.cxx:2242
#9  0x00007fffbd543956 in TStreamerInfo::WriteBufferAux<char**> (this=0x1fdb5b0, b=...,
    arr=@0x7fffffffb808: 0x7fffffffb800, compinfo=0x35c9a28, first=first@entry=0, last=last@entry=1, narr=1,
    eoffset=0, arrayMode=0)
    at /.../ROOT/root_v6.24.02/io/io/src/TStreamerInfoWriteBuffer.cxx:628
#10 0x00007fffbd3997cd in TStreamerInfoActions::GenericWriteAction (buf=..., addr=<optimized out>,
    config=<optimized out>)
    at /.../ROOT/root_v6.24.02/io/io/src/TStreamerInfoActions.cxx:201
#11 0x00007fffbd2d1525 in TStreamerInfoActions::TConfiguredAction::operator() (object=0x2ca39c0, buffer=..., this=
    0x35c6950)
    at /.../ROOT/root_v6.24.02/io/io/inc/TStreamerInfoActions.h:123
#12 TBufferFile::ApplySequence (obj=0x2ca39c0, sequence=..., this=0x46f7ca10)
    at /.../ROOT/root_v6.24.02/io/io/src/TBufferFile.cxx:3572
#13 TBufferFile::WriteClassBuffer (this=0x46f7ca10, cl=0x2cd48b0, pointer=0x2ca39c0)
 at /.../ROOT/root_v6.24.02/io/io/src/TBufferFile.cxx:3541
#14 0x00007fffbd373ccc in TKey::TKey (this=this@entry=0x477c74c0, obj=obj@entry=0x2ca39c0,
    name=name@entry=0x2ca39d9 "RooTrackerTree", bufsize=bufsize@entry=35872, motherDir=motherDir@entry=0x1fb8f40)
    at /.../ROOT/root_v6.24.02/io/io/src/TKey.cxx:249
#15 0x00007fffbd332605 in TFile::CreateKey (this=<optimized out>, mother=0x1fb8f40, obj=0x2ca39c0,
    name=0x2ca39d9 "RooTrackerTree", bufsize=35872)
    at /.../ROOT/root_v6.24.02/io/io/src/TFile.cxx:979
#16 0x00007fffbd323fd5 in TDirectoryFile::WriteTObject (this=0x1fb8f40, obj=0x2ca39c0, name=<optimized out>,
    option=<optimized out>, bufsize=0)
    at /.../ROOT/root_v6.24.02/io/io/src/TDirectoryFile.cxx:19
#17 0x00007fffbd7c24da in TObject::Write (this=0x2ca39c0, name=0x0, option=<optimized out>, bufsize=0) at /xxx/ROOT/root_v6.24.02/core/base/src/TObject.cxx:784

What should I do?

Here is the more detailed code

class A : public TObject{
private:
TFile* fFile;
TTree* fTree;
static std::map<std::string, shared_ptr<A>> map_a;
public:
static shared_ptr<A> A::Open(name){
    auto itr = map_a.find(name);
    if(itr != map_a.end()) return itr->second;
    shared_ptr<A> a(new A(name));
    map_a[name] = a;
    return a;
}
A::A(name){
    TFile::Open(name, mode);
    fTree = new TTree(treename, description);
    fTree->SetDirectory(fFile);
    fTree->SetAutoSave(100);
    //SetBranches here
    gROOT->GetListOfFiles()->Remove(fFile);
}
A::~A(){
    fFile->cd();
    fTree->Write();
    //other things
    fFile->Close();
}

std::map<std::string, shared_ptr<A>> A::map_a;
int main(){
    shared_ptr<A> a = A::Open(name, mode);
    //something
}

Try replacing:

with

fTree= new TTree("datatree", "description of the data tree");

In that code, fFile is never set and thus any use will lead to random behavior (likely segfault).

    fFile->cd();
    fTree->Write();

a much better option is just:

   fFile->Write();

Ah sorry I simplified the code to make it clear, but it seems not…I modified the code now.
it’s actually fTree = new TTree(treename, description);.
In Close() it’s because I opened multiple files and TTrees, can I still use fFile->Write() for some specific TTree?
And I don’t understand what do you mean fFile is never set?

Augment of this, If I remove gROOT->GetListOfFiles()->Remove(fFile); then segfault won’t occur at fTree->Write() (but will occur at the end of program because both static map created by me and by gROOT will try to delete the TFile, that’s why I need to remove it from gROOT)

(but will occur at the end of program because both static map

It is more likely than not that the order of library unloading vs file closing during the tear down is ‘wrong’. Using a static containers to manage the lifetime of the TFile is ‘hard’ ™ and getting the gROOT list of files to behaves nicely requires help from the tear down ordering and is still not always right.

The ‘simplest’ solution is to make sure that the static map is explicitly cleared (map_a.clear()) *before* the end of the ‘main’ routine.

To see what is happening in more details, run the failing program with valgrind --suppressions=$ROOTSYS/etc/valgrind-root.supp ...

Sorry just to make me clear, you are saying it’s necessary to keep TFiles in gROOT list of files to make it working with full functionalities?
If that’s the case I’ll have to give up removing it from gROOT…I want to avoid explicitly calling clear() every program since I will always forget it someday.

Not quite. I am saying that implementing the memory management related to handling of a static list of TFile (both your map and gROOT->GetListOfFiles()) are an example of such a static list) is really hard.

I want to avoid explicitly calling clear() every program since I will always forget it someday.

That is a fair challenge and why the design recommendation have move toward RAII concepts.

A ‘simple’ solution is to ‘require’ every program to explicit create an object. For example:

class A : public TObject {
   TFile *fFile = nullptr;
   TTree *fTree = nullptr;
public:
    A(const char *name);
   ~A() { 
      f (fTree)
         fTree->GetFile()->Write(); 
      delete fFile;
    }
};
class AFactory {
   std::map<std::string, shared_ptr<A>> map_a;
public:
   shared_ptr<A> Open(const char* name){
      auto itr = map_a.find(name);
      if(itr != map_a.end()) return itr->second;
      shared_ptr<A> a(new A(name));
      map_a[name] = a;
      return a;
  }
};
int main()
{
    AFactory factory;
    auto a1 = factory.Open(filename1);
}

i.e. unless you really really have to, don’t use any global statics.

Not quite. I am saying that implementing the memory management related to handling of a static list of TFile (both your map and gROOT->GetListOfFiles() ) are an example of such a static list) is really hard.

Thank you very much to make it clear!

A ‘simple’ solution is to ‘require’ every program to explicit create an object

Don’t the factory need to be a singleton? Otherwise I won’t be able to call it in different classes. (And if it’s singleton then it’s static…)

And I still don’t understand why I can’t simply remove TFile from gROOT…

And I still don’t understand why I can’t simply remove TFile from gROOT…

The problem is somewhat unrelated to that … Removing it from there run just fine, it is the deletion of the std::map during the tear down that is the issue. Please see the result of valgrind for the actual details.

There is various ways of fixing this. The most straight-forward is to pass the factory (or yet another object contains multiple objects) to all the places that needs it. [There is other possible more complicated choices involving the static being just a kind of reference or pointer that is properly reset by the destruction of the factory at the end of main]

Please see the result of valgrind for the actual details.

Do you mean run valgrind --suppressions=$ROOTSYS/etc/valgrind-root.supp myProgram?

to all the places that needs it. [There is other possible more complicated choices involving the static being just a kind of reference or pointer that is properly reset by the destruction of the factory at the end of main]

I assume you are talking about singleton (if not please correct me), If I use a factory class with a singleton mode, will the static pointer to the factory cause the same problem? The problem I mean std::map in that static object will try to delete TFile where gROOT already did it.

Yes.

If I use a factory class with a singleton mode, will the static pointer to the factory cause the same problem?

Possibly. To avoid the problem, you need to design the singleton such that its actually deleted at the end of the main function. It is possible to do but challenging and/or fragile. The better options is to have a ‘Configuration’ style object that contain this factory and any other ‘global’ state/configuration and pass it around.

I used valgrind to run it, but seems the information is the same as gdb, could you please have a look and tell what was wrong?

==2420874== Invalid read of size 8
==2420874==    at 0x3F03CB5A: GetBaseClassOffset (TClass.cxx:2791)
==2420874==    by 0x3F03CB5A: TClass::GetBaseClassOffset(TClass const*, void*, bool) (TClass.cxx:2769)
==2420874==    by 0x3F31F359: WriteObjectAny (TBufferIO.cxx:518)
==2420874==    by 0x3F31F359: TBufferIO::WriteObjectAny(void const*, TClass const*, bool) (TBufferIO.cxx:492)
==2420874==    by 0x3F006FDC: operator<< <TObject> (TBuffer.h:402)
==2420874==    by 0x3F006FDC: TObjArray::Streamer(TBuffer&) (TObjArray.cxx:487)
==2420874==    by 0x3F312AF2: Streamer (TClass.h:609)
==2420874==    by 0x3F312AF2: WriteFastArray (TBufferFile.cxx:2255)
==2420874==    by 0x3F312AF2: TBufferFile::WriteFastArray(void*, TClass const*, int, TMemberStreamer*) (TBufferFile.cxx:2242)
==2420874==    by 0x3F58A955: int TStreamerInfo::WriteBufferAux<char**>(TBuffer&, char** const&, TStreamerInfo::TCompInfo* const*, int, int, int, int, int) (TStreamerInfoWriteBuffer.cxx:628)
==2420874==    by 0x3F3E07CC: TStreamerInfoActions::GenericWriteAction(TBuffer&, void*, TStreamerInfoActions::TConfiguration const*) (TStreamerInfoActions.cxx:201)
==2420874==    by 0x3F318524: operator() (TStreamerInfoActions.h:123)
==2420874==    by 0x3F318524: ApplySequence (TBufferFile.cxx:3572)
==2420874==    by 0x3F318524: TBufferFile::WriteClassBuffer(TClass const*, void*) (TBufferFile.cxx:3541)
==2420874==    by 0x3F3BACCB: TKey::TKey(TObject const*, char const*, int, TDirectory*) (TKey.cxx:249)
==2420874==    by 0x3F379604: TFile::CreateKey(TDirectory*, TObject const*, char const*, int) (TFile.cxx:979)
==2420874==    by 0x3F36AFD4: TDirectoryFile::WriteTObject(TObject const*, char const*, char const*, int) (TDirectoryFile.cxx:1918)
==2420874==    by 0x3EF8A4D9: TObject::Write(char const*, int, int) const (TObject.cxx:784)
==2420874==    by 0x3E58CC2A: COMET::IRooTrackerFile::Close() (IRooTrackerFile.cxx:234)
==2420874==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==2420874==
==2420874==
==2420874== Process terminating with default action of signal 11 (SIGSEGV)
==2420874==  Access not within mapped region at address 0x0
==2420874==    at 0x3F03CB5A: GetBaseClassOffset (TClass.cxx:2791)
==2420874==    by 0x3F03CB5A: TClass::GetBaseClassOffset(TClass const*, void*, bool) (TClass.cxx:2769)
==2420874==    by 0x3F31F359: WriteObjectAny (TBufferIO.cxx:518)
==2420874==    by 0x3F31F359: TBufferIO::WriteObjectAny(void const*, TClass const*, bool) (TBufferIO.cxx:492)
==2420874==    by 0x3F006FDC: operator<< <TObject> (TBuffer.h:402)
==2420874==    by 0x3F006FDC: TObjArray::Streamer(TBuffer&) (TObjArray.cxx:487)
==2420874==    by 0x3F312AF2: Streamer (TClass.h:609)
==2420874==    by 0x3F312AF2: WriteFastArray (TBufferFile.cxx:2255)
==2420874==    by 0x3F312AF2: TBufferFile::WriteFastArray(void*, TClass const*, int, TMemberStreamer*) (TBufferFile.cxx:2242)
==2420874==    by 0x3F58A955: int TStreamerInfo::WriteBufferAux<char**>(TBuffer&, char** const&, TStreamerInfo::TCompInfo* const*, int, int, int, int, int) (TStreamerInfoWriteBuffer.cxx:628)
==2420874==    by 0x3F3E07CC: TStreamerInfoActions::GenericWriteAction(TBuffer&, void*, TStreamerInfoActions::TConfiguration const*) (TStreamerInfoActions.cxx:201)
==2420874==    by 0x3F318524: operator() (TStreamerInfoActions.h:123)
==2420874==    by 0x3F318524: ApplySequence (TBufferFile.cxx:3572)
==2420874==    by 0x3F318524: TBufferFile::WriteClassBuffer(TClass const*, void*) (TBufferFile.cxx:3541)
==2420874==    by 0x3F3BACCB: TKey::TKey(TObject const*, char const*, int, TDirectory*) (TKey.cxx:249)
==2420874==    by 0x3F379604: TFile::CreateKey(TDirectory*, TObject const*, char const*, int) (TFile.cxx:979)
==2420874==    by 0x3F36AFD4: TDirectoryFile::WriteTObject(TObject const*, char const*, char const*, int) (TDirectoryFile.cxx:1918)
==2420874==    by 0x3EF8A4D9: TObject::Write(char const*, int, int) const (TObject.cxx:784)
==2420874==    by 0x3E58CC2A: COMET::IRooTrackerFile::Close() (IRooTrackerFile.cxx:234)
==2420874==  If you believe this happened as a result of a stack
==2420874==  overflow in your program's main thread (unlikely but
==2420874==  possible), you can try to increase the size of the
==2420874==  main thread stack using the --main-stacksize= flag.
==2420874==  The main thread stack size used in this run was 8388608.

So we have:

==2420874== Invalid read of size 8
==2420874==    at 0x3F03CB5A: GetBaseClassOffset (TClass.cxx:2791)
==2420874==    by 0x3F03CB5A: TClass::GetBaseClassOffset(TClass const*, void*, bool) (TClass.cxx:2769)
....
==2420874==    by 0x3E58CC2A: COMET::IRooTrackerFile::Close() (IRooTrackerFile.cxx:234)
==2420874==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

We don’t see the full stack trace (add --num-callers=100 to the valgrind command line to get more information) but given the description you gave I am assuming that the callers ultimately is _exit.

That stack trace indicates that upon doing the Write requested by COMET::IRooTrackerFile::Close, some user objects is being stored (actually it might even just be TBranch) and the fact that GetBaseClassOffset is using a nullptr is indicating that the library that contains the compiled code and the dictionary for that type of objects has already been teared down (unloaded).

This is (for better or worse) more or so what I expected.

A TFile::Write must be executed before

  • any of the libraries for the objects that will be written has been unloaded
  • gROOT and gCling are deleted/teared-down.

The only way to guaranteed this is to execute the TFile::Write before the execution of the return of the main function. Other techniques including but not limited to the one used by TROOT or by manipulatiing atexit or by using function statics might work in some circumstances but are fragile and will fail in many circumstances.

You are right, it is called by exit function:

==2648936== Invalid read of size 8
==2648936==    at 0x3F03CB5A: GetBaseClassOffset (TClass.cxx:2791)
==2648936==    by 0x3F03CB5A: TClass::GetBaseClassOffset(TClass const*, void*, bool) (TClass.cxx:2769)
==2648936==    by 0x3F31F359: WriteObjectAny (TBufferIO.cxx:518)
==2648936==    by 0x3F31F359: TBufferIO::WriteObjectAny(void const*, TClass const*, bool) (TBufferIO.cxx:492)
==2648936==    by 0x3F006FDC: operator<< <TObject> (TBuffer.h:402)
==2648936==    by 0x3F006FDC: TObjArray::Streamer(TBuffer&) (TObjArray.cxx:487)
==2648936==    by 0x3F312AF2: Streamer (TClass.h:609)
==2648936==    by 0x3F312AF2: WriteFastArray (TBufferFile.cxx:2255)
==2648936==    by 0x3F312AF2: TBufferFile::WriteFastArray(void*, TClass const*, int, TMemberStreamer*) (TBufferFile.cxx:2242)
==2648936==    by 0x3F58A955: int TStreamerInfo::WriteBufferAux<char**>(TBuffer&, char** const&, TStreamerInfo::TCompInfo* const*, int, int, int, int, int) (TStreamerInfoWriteBuffer.cxx:628)
==2648936==    by 0x3F3E07CC: TStreamerInfoActions::GenericWriteAction(TBuffer&, void*, TStreamerInfoActions::TConfiguration const*) (TStreamerInfoActions.cxx:201)
==2648936==    by 0x3F318524: operator() (TStreamerInfoActions.h:123)
==2648936==    by 0x3F318524: ApplySequence (TBufferFile.cxx:3572)
==2648936==    by 0x3F318524: TBufferFile::WriteClassBuffer(TClass const*, void*) (TBufferFile.cxx:3541)
==2648936==    by 0x3F3BACCB: TKey::TKey(TObject const*, char const*, int, TDirectory*) (TKey.cxx:249)
==2648936==    by 0x3F379604: TFile::CreateKey(TDirectory*, TObject const*, char const*, int) (TFile.cxx:979)
==2648936==    by 0x3F36AFD4: TDirectoryFile::WriteTObject(TObject const*, char const*, char const*, int) (TDirectoryFile.cxx:1918)
==2648936==    by 0x3EF8A4D9: TObject::Write(char const*, int, int) const (TObject.cxx:784)
==2648936==    by 0x3E58CC2A: COMET::IRooTrackerFile::Close() (IRooTrackerFile.cxx:234)
==2648936==    by 0x3E58D06A: COMET::IRooTrackerFile::~IRooTrackerFile() (IRooTrackerFile.cxx:46)
==2648936==    by 0x3E58D108: COMET::IRooTrackerFile::~IRooTrackerFile() (IRooTrackerFile.cxx:47)
==2648936==    by 0x3EC077A6: COMET::IHandleBaseDeletable::~IHandleBaseDeletable() (IHandle.cxx:36)
==2648936==    by 0x3EC077B8: COMET::IHandleBaseDeletable::~IHandleBaseDeletable() (IHandle.cxx:37)
==2648936==    by 0x3E58B0D8: ~IHandle (IHandle.hxx:238)
==2648936==    by 0x3E58B0D8: ~pair (stl_pair.h:211)
==2648936==    by 0x3E58B0D8: destroy<std::pair<const std::__cxx11::basic_string<char>, COMET::IHandle<COMET::IRooTrackerFile> > > (new_allocator.h:168)
==2648936==    by 0x3E58B0D8: destroy<std::pair<const std::__cxx11::basic_string<char>, COMET::IHandle<COMET::IRooTrackerFile> > > (alloc_traits.h:535)
==2648936==    by 0x3E58B0D8: _M_destroy_node (stl_tree.h:623)
==2648936==    by 0x3E58B0D8: _M_drop_node (stl_tree.h:631)
==2648936==    by 0x3E58B0D8: std::_Rb_tree<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, COMET::IHandle<COMET::IRooTrackerFile> >, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, COMET::IHandle<COMET::IRooTrackerFile> > >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, COMET::IHandle<COMET::IRooTrackerFile> > > >::_M_erase(std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, COMET::IHandle<COMET::IRooTrackerFile> > >*) [clone .isra.0] (stl_tree.h:1891)
==2648936==    by 0x41E0C2A6: __cxa_finalize (in /usr/lib64/libc.so.6)
==2648936==    by 0x3E588FC6: ??? (in /gpfs/group/had/muon/ssun/ICEDUST_master/ICEDUST_install/oaRooTracker/lib/liboaRooTracker.so)
==2648936==    by 0x4004E2D: _dl_fini (dl-fini.c:142)
==2648936==    by 0x41E0BDEC: __run_exit_handlers (in /usr/lib64/libc.so.6)
==2648936==    by 0x41E0BF2F: exit (in /usr/lib64/libc.so.6)
==2648936==    by 0x41DF4596: (below main) (in /usr/lib64/libc.so.6)
==2648936==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

And in the root v6.24.02 TClass.cxx:2791 is this line:

return gCling->ClassInfo_GetBaseOffset(derived, base, address, isDerivedObject);

which means gCling has been deleted in the scope. But it’s quite strange to me because TFile class requires gCling to be fully functioning, shouldn’t gCling (core) be teared down after TFile (io)?

My guess in that in your case (but don’t count on it to be stable for other platforms or builds), that the order of operations is as follow:

  1. the main function exits
  2. the atexit for libCore is run
  3. thus ~TROOT is executed and thus ~TCling is executed.
  4. the atexit for the library containing your A class is run
  5. thus the std::map<std::string, shared_ptr<A>> destructor is called
  6. thus the file is being written
  7. segmentation fault (what follows would happen if the process did not crash.
  8. dlclose (per se) of libRIO
  9. dlclose (per se) of libCore
  10. dlclose (per se) of the library containing your A class
  11. actual end of process.

Step 4. through 6. are shown on the last stack trace.

The order of the atexit and the order library unloading is done in reverse order of library loading (and technically for atexit in reverse order of registration).

Because libCore is usually loaded after the some/most user libraries, the file closing done by ~TROOT is usually (but not always) successful. If you want to bet on that (instead of using the more reliable solution I sketched earlier), you could leave the files in the GetListOfFiles and add one of your objects in the GetListOfCleanups. When a file (actually any TObject) is deleted the function RecursiveRemove is called on each of the element of the list, you could then remove the file from your maps. (Even if it is done right, there might still be times (i.e. link and/or library load orderings) that will still fail).

Indeed passing the factory to everywhere is the most safe method, but it will increase the number of parameters passing to functions…and will be worse if there are a lot of different factory objects.

I just got an idea, how about judge if ROOT::Internal::gROOTLocal is nullptr, if it is then I don’t need to do anything to TFiles. But I’m assuming TFile::~TFile() will automatically write every objects (like TTree), is that the case?

This idea is because I found that gROOTLocal = nullptr; will be done at the very end of TROOT::~TROOT()