rexyai / RestRserve

R web API framework for building high-performance microservices and app backends
https://restrserve.org
271 stars 31 forks source link

MacOS: "+[NSNumber initialize] may have been in progress in another thread when fork() was called." #195

Closed richarddmorey closed 1 year ago

richarddmorey commented 1 year ago

Issue

Under a very specific set of circumstances, RestRserve fails to serve a page on MacOS.

On macOS 12.6 (21G115), under these three conditions:

The server fails to give a response:

$ curl http://localhost:8080/a
curl: (52) Empty reply from server

In the terminal running the server, we see

$ Rscript start.R
Installing cli [3.4.1] ...
        OK [linked cache]
{"timestamp":"2022-11-12 09:06:50.916110","level":"INFO","name":"Application","pid":74331,"msg":"","context":{"http_port":8080,"endpoints":{"HEAD":"/a","GET":"/a"}}}
-- running Rserve in this R session (pid=74331), 2 server(s) --
(This session will block until Rserve is shut down)
objc[74414]: +[NSNumber initialize] may have been in progress in another thread when fork() was called.
objc[74414]: +[NSNumber initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.

This does not seem to occur in Ubuntu 20.04.5 LTS.

Reproducing

To reproduce, I've created a repository here with a MRE: https://github.com/richarddmorey/testRestRserve

  1. Download the repository
  2. Install the necessary packages with {renv}:
renv::restore()
  1. In a terminal, start the server:
$ Rscript start.R
  1. In another terminal, try to get a response:
curl http://localhost:8080/a

Removing {cli}, not loading {promises}, or not calling rmarkdown::render will eliminate the errors.

Cause

I don't know enough to effectively debug this myself, given the fact that it seems to be an interaction between several packages. However, it is suggested here with a similar error in ruby that

"Apple's Objective-C libraries have traditionally not supported being called in a forked (but not exec'd) child process at all, but since High Sierra 10.13 they've tried to add limited support for this. However in doing so they've also defined rules on what is not allowed after forking. One of the rules state that it is not allowed to call the initialize function of certain Objective-C classes after forking; that may only happen before forking."

My read of this is that it happens because of the anarchic nature of packages, and some forbidden initialization when {rmarkdown} loads after RestRserve forks off the process...? I don't understand why merely having {cli} installed would cause a problem, though.

Workaround

I've found that loading {rmarkdown} library at the start fixes the problem, but this might not be general enough.

In this thread it is suggested to add an environment variable before starting the server:

export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

I don't know if this has bad implications, but it seems to solve the problem.

s-u commented 1 year ago

You cannot pre-load any packages or libraries that are not fork-safe.

In this particular case the backtrace shows that knitr (via rmarkdown) is using the Quartz device which is not fork-safe if CF has been initialized before, so in this case simply use fork-safe devices like Cairo, e.g. by adding

knitr::opts_chunk$set(dev="CairoPNG")
richarddmorey commented 1 year ago

Thanks for that. Is there any way of telling beforehand whether an R package includes non-fork-safe functions?

dselivanov commented 1 year ago

I don't think so. Rule of thumb is that anything which involves system UI, jvm are likely not fork safe.