[nanomsg] Re: [Non-DoD Source] Re: On pthread_atfork(), and fork()-safe implementation

  • From: "Karan, Cem F CIV USARMY RDECOM ARL (US)" <cem.f.karan.civ@xxxxxxxx>
  • To: "nanomsg@xxxxxxxxxxxxx" <nanomsg@xxxxxxxxxxxxx>
  • Date: Wed, 14 Dec 2016 18:37:34 +0000

As you wish.  Can you post how you handle the memory leaks after the fork()?  
I'm curious to see what you're going to do.

Thanks,
Cem Karan

-----Original Message-----
From: nanomsg-bounce@xxxxxxxxxxxxx [mailto:nanomsg-bounce@xxxxxxxxxxxxx] On ;
Behalf Of Garrett D'Amore
Sent: Wednesday, December 14, 2016 10:57 AM
To: nanomsg@xxxxxxxxxxxxx
Subject: [nanomsg] Re: [Non-DoD Source] Re: On pthread_atfork(), and 
fork()-safe implementation

All active links contained in this email were disabled. Please verify the 
identity of the sender, and confirm the authenticity of all links
contained within the message prior to copying and pasting the address to a 
Web browser.


________________________________



Yeah…um… as an operating system engineer, I generally believe I can keep 
track of my own objects.  (Kind of critical when you work inside
a kernel.)  In fact, in libnng I require that the system provide the *size* 
of the object with the object at free() time.  This is to permit
porting to platforms where this is a requirement.  (Such a requirement exists 
in the Solaris kernel.)  It also allows the use of much much
more efficient allocators, like slab allocators, and not having to stash the 
size with the object (which is often automatically known at
*compile* time), so you save quite a bit of lookup, and can improve the odds 
of having your object aligned on a natural boundary (such as
a page).

Portability to embedded systems is really important to me in this effort, and 
so a GC is kind of out of the question.


On Wed, Dec 14, 2016 at 6:50 AM, Michael Powell <mwpowellhtx@xxxxxxxxx < 
Caution-mailto:mwpowellhtx@xxxxxxxxx ;> > wrote:


      On Wed, Dec 14, 2016 at 9:07 AM, Karan, Cem F CIV USARMY RDECOM ARL
      (US) <cem.f.karan.civ@xxxxxxxx < 
Caution-mailto:cem.f.karan.civ@xxxxxxxx ;> > wrote:
      > Have you considered using a garbage collector?  E.g. 
Caution-http://www.hboehm.info/gc/ ;< Caution-
http://www.hboehm.info/gc/ ;> .  Looking through the header file, it appears 
that there are calls specifically for handling forks
(GC_set_handle_fork(), GC_atfork_prepare(), GC_atfork_parent(), 
GC_atfork_child(), and GC_start_mark_threads()).  Based on the
documentation surrounding GC_start_mark_threads(), it appears that the 
collector can handle fork()s that are not followed by an exec().
That may solve the memory leak issues cleanly.  There are also functions to 
register finalization methods, so that should handle dealing
with file pointers, etc. that you want to close eventually.

      What does a GC gain you, but to make further excuses for poor coding
      practices, in the first place? Been there, done that, don't need
      another T-shirt.


      > I use that particular collector in my own work, and it is quite fast; 
I allocate a ridiculous number of short-lived objects, and even
then, the profiler shows that garbage collection takes less than 1% of the 
runtime.  I've never tried forking a child though, so I don't know
how well that part works.
      >
      > Thanks,
      > Cem Karan
      >
      >> -----Original Message-----
      >> From: nanomsg-bounce@xxxxxxxxxxxxx < 
Caution-mailto:nanomsg-bounce@xxxxxxxxxxxxx ;>  [Caution-mailto:nanomsg-
bounce@xxxxxxxxxxxxx < Caution-mailto:nanomsg-bounce@xxxxxxxxxxxxx ;> ] On 
Behalf Of Garrett D'Amore
      >> Sent: Wednesday, December 14, 2016 1:53 AM
      >> To: nanomsg@xxxxxxxxxxxxx < Caution-mailto:nanomsg@xxxxxxxxxxxxx ;>
      >> Subject: [Non-DoD Source] [nanomsg] Re: On pthread_atfork(), and 
fork()-safe implementation
      >>
      >> All active links contained in this email were disabled. Please 
verify the identity of the sender, and confirm the authenticity of all
links
      >> contained within the message prior to copying and pasting the 
address to a Web browser.
      >>
      >>
      >> ________________________________
      >>
      >>
      >>
      >> Well I thought I had a brilliant idea, and I spent a number of hours 
this evening trying to bake in a solution.  I eventually had to
throw my
      >> hands up in the air.
      >>
      >> I can see that it *is* possible to build a solution that leaks 
*only* any memory used by mutexes and condvars.  That’s definitely
possible.
      >> The problem is, the work you have to do for this is extreme, and it 
requires you to basically build the equivalent of an operating
system in
      >> some ways.  I had a scheme to suspend threads, and mark regions 
fork-safe vs. unsafe, etc.  The problem is that in order to
avoid leaking
      >> memory, you pretty *have* to manage your own heap — as in every 
single memory object in your system has to be globally
discoverable.
      >> This turns out to be rather inconvenient if you don’t also want to 
build your own memory manager, since some memory
objects are going
      >> to be used by threads, and frankly I had objects that were 
“orphaned” in that they didn’t have any global state to them, only
locally used
      >> inside functions in threads.
      >>
      >> One day I may come back to this, by supplying my own memory manager 
that will let me reclaim every allocated object in the
system
      >> (perhaps simply by reclaiming the entire heap in one fell swoop).  
I’d also need a way to reclaim files, and handle mutexes and
condvars
      >> “magically”.  I’m pretty sure I know how to do that, and that it can 
be done in the platform layer.  Which means it can be done
in the
      >> future, as a fairly straight-forward retrofit, once I decide I’m 
willing to take the larger action to stop using “ordinary” memory
      >> management.  I’ve got enough other stuff to do in the meantime, that 
I’m taking my earlier action, which is to panic when the
user
      >> attempts to reenter the library from the child after fork().
      >>
      >>  - Garrett
      >>
      >>
      >> On Tue, Dec 13, 2016 at 8:51 AM, Garrett D'Amore <garrett@xxxxxxxxxx 
< Caution-mailto:garrett@xxxxxxxxxx ;>  < Caution-
Caution-mailto:garrett@xxxxxxxxxx ;< Caution-mailto:garrett@xxxxxxxxxx ;>  > > 
wrote:
      >>
      >>
      >>       Thanks.  I had planned to design a fork safe version of things 
in the new design. I had implemented freeze and thaw and
reset
      >> entry points at various points and was pretty sure that this would 
have worked well.  Until I discovered that the child side
version was not
      >> allowed to call any mutex functions or to call free.
      >>
      >>       I will think about this some more.  Delaying the child side 
action might be reasonable and lead to a working solution.
      >>
      >>       Sent from my iPhone
      >>
      >>
      >>       > On Dec 13, 2016, at 12:20 AM, Franklin Mathieu 
<franklinmathieu@xxxxxxxxx < Caution-
mailto:franklinmathieu@xxxxxxxxx ;>  < 
Caution-Caution-mailto:franklinmathieu@xxxxxxxxx ;< Caution-
mailto:franklinmathieu@xxxxxxxxx ;>  >
      >> > wrote:
      >>       >
      >>       > I'm going to give my 2 cents on the matter as I was the one 
that initially
      >>       > opened the github issue regarding fork()-safety and I had 
the time
      >>       > to work with different approaches on the matter.
      >>       >
      >>       > I've been maintaining an unit testing framework for C that 
relies on
      >>       > worker processes to run tests safely, and as such, for the 
longest
      >>       > time, this had been implemented with fork() without a 
subsequent exec().
      >>       > I recently switched the I/O layer of the framework to use 
nanomsg
      >>       > because it was simple, and it was much more "correct" than 
what
      >>       > I had been doing before with pipe() shenanigans.
      >>       >
      >>       > However, as nanomsg isn't fork()-safe, I took a swab at 
implementing
      >>       > a fork()-safety mechanism, which ended up being brittle but 
was
      >>       > "good enough" for my purposes, and I reworked other 
dependencies
      >>       > to make sure they handled forks correctly.
      >>       >
      >>       > The problem with fork()-safety is that unless you think of 
it right at the
      >>       > design of the software, you're going to end up doing 
something hack-ish;
      >>       > which means that the rewrite could be a good starting point 
to actually
      >>       > implement the structural basis towards fork()-safety. POSIX 
might be
      >>       > right on target with the problems caused by 
pthread_atfork(), but in
      >>       > practice there is a lot of wiggle room to do what we must to 
make
      >>       > things work at fork.
      >>       >
      >>       > With all of that being said, I've given up myself on 
fork()-safety.
      >>       > The fact is that there is no single silver bullet to address 
this,
      >>       > that a lot of software is expecting exec() to be called 
after a fork(),
      >>       > and that there aren't many use cases in having worker 
processes.
      >>       >
      >>       > I ended up writing a library dedicated to spawning worker
      >>       > processes [1] in a manner that calls fork() then re-exec()s 
the current
      >>       > executable with a patched main function, which while not 
ideal, is
      >>       > in my opinion less of a hack than having to make the 
software and
      >>       > all of its dependencies fork-safe().
      >>       >
      >>       > This is why I understand your decision of giving up and 
panicking
      >>       > the process on fork-reentry. You might also be able to 
compromise
      >>       > by only allowing calls to nng_socket_create after fork, 
which could under
      >>       > the covers completely drop the current invalid state and 
just reinitialize
      >>       > the library. This would cause a resource leak, but allow the 
usage
      >>       > of sockets in the child for those that really want it.
      >>       >
      >>       > [1]: Caution-Caution-https://github.com/diacritic/BoxFort ;< 
Caution-https://github.com/diacritic/BoxFort ;>  < Caution-
Caution-https://github.com/diacritic/BoxFort ;< 
Caution-https://github.com/diacritic/BoxFort ;>  >
      >>       >
      >>       > 2016-12-12 19:31 GMT+01:00 Garrett D'Amore 
<garrett@xxxxxxxxxx < Caution-mailto:garrett@xxxxxxxxxx ;>  <
Caution-Caution-mailto:garrett@xxxxxxxxxx ;< Caution-mailto:garrett@xxxxxxxxxx ;
 > >:
      >>       >> The following conversation relates to using fork() with 
nanomsg (or future
      >>       >> rewrites), where you do *not* immediately call exec().  
Using fork() and
      >>       >> then immediately calling exec() is fine, and will continue 
to work as it
      >>       >> always.
      >>       >>
      >>       >> But some people want to use fork() to spawn children, e.g. 
a child worker
      >>       >> process, that communicates back to the parent somehow.   
This is never going
      >>       >> to work.
      >>       >>
      >>       >> I’ve been doing a bit more research into pthread_atfork() 
as part of an
      >>       >> attempt to make my new nng library properly fork()-safe.  
I’ve more or less
      >>       >> given up though.
      >>       >>
      >>       >> The reason for this is that even the OpenGroup has given up 
— see
      >>       >> 
Caution-Caution-http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html
 < Caution-
http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html ;
 < Caution-
      >> 
Caution-http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html
 < Caution-
http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html ;
 >
      >>       >> — and especially the RATIONALE section, for the logic 
behind this.  They
      >>       >> have even indicated plans to deprecate the pthread_atfork() 
API altogether.
      >>       >>
      >>       >> Essentially, it isn’t possible to make a version of the 
library fork() safe
      >>       >> as it would be necessary to free resources, do locks, etc. 
— i.e. all those
      >>       >> Async-Signal-Unsafe calls.
      >>       >>
      >>       >> So, for libnng, and possibly in the future for libnanomsg, 
I will be
      >>       >> changing the API so that if you attempt to callback into 
the library after
      >>       >> fork(), it will actually panic the process.
      >>       >>
      >>       >> I probably will also arrange for pthread_atfork() to be 
called to close any
      >>       >> file descriptors that were not marked close-on-exec…
      >>       >>
      >>       >> Stay tuned for more details.
      >>       >>
      >>       >> - Garrett
      >>       >
      >>       >
      >>       > --
      >>       > Franklin "Snaipe" Mathieu
      >>       > 🝰 Caution-Caution-https://diacritic.io ;< 
Caution-https://diacritic.io ;>  < Caution-Caution-https://diacritic.io ;< 
Caution-
https://diacritic.io ;>  >
      >>       >
      >>
      >>
      >




Attachment: smime.p7s
Description: S/MIME cryptographic signature

Other related posts: