Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crashes if first use is inside a fork (ex. spring) #206

Open
woahdae opened this issue Nov 4, 2021 · 10 comments
Open

Crashes if first use is inside a fork (ex. spring) #206

woahdae opened this issue Nov 4, 2021 · 10 comments

Comments

@woahdae
Copy link

woahdae commented Nov 4, 2021

At some point along my upgrade path from Big Sur (11.6) to Monterey (12.0.1) and x86 to arm/m1 (OS upgrade probably more relevant than processed arch but who knows), when I do:

Process.fork do
  Ethon::Easy.new(url: 'www.example.com').perform
end

I get the error:

ETHON: Libcurl initialized
objc[81096]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[81096]: +[__NSCFConstantString 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.

However, if I do:

Ethon::Easy.new(url: 'www.example.com').perform
Process.fork do
  Ethon::Easy.new(url: 'www.example.com').perform
end

^ works as expected.

I tried just calling Ethon::Curl.init prior to the fork, which didn't resolve the issue.

Dug into the code a bit to try to see if I could find what's happening, but it looks like there's some shared state or underlying resources and it might take a while to grok. Hoped maybe someone here would have a more immediate insight.

My real issue is connecting to elasticsearch in a spring-enabled environment, so my workaround is to call Ethon::Easy.new(url: 'localhost').perform in an initializer, i.e. before spring loads, so when spring forks it'll not crash my server.

@davidstosik
Copy link

Thanks, this helped me understand what's going on.
Won't be a solution for all, but seeing as Rails 7 is dropping Spring by default, some people might be glad to hear it should work when disabling Spring. I reproduced the problem in bin/rails c and it worked when I used DISABLE_SPRING=1 bin/rails c instead.

@directionless
Copy link

directionless commented Jan 29, 2022

I think it's not just spring -- I'm starting to see this in my dev environment. I can't quite pin it down, might be something in sidekiq.

@semaperepelitsa
Copy link
Contributor

This also happens when running a Puma web server in clustered mode (forked worker processes). I have isolated the problem to this:

easy = Ethon::Easy.new(url: "http://example.com")
handle = easy.handle
fork { Ethon::Curl.easy_perform(handle) }

objc[20103]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[20103]: +[__NSCFConstantString 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.

FATAL: prematurely zombied

So the error occurs inside curl_easy_perform C function.

@semaperepelitsa
Copy link
Contributor

May be caused by FFI? Couldn't reproduce the error in C.

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <curl/curl.h>

int main(void)
{
  pid_t pid = fork();
  if(pid == 0) {
    CURL *curl;
    CURLcode res;
    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();
    if(curl) {
      curl_easy_setopt(curl, CURLOPT_URL, "https://api.ipify.org");
      res = curl_easy_perform(curl);
      if(res != CURLE_OK)
        fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(res));
      curl_easy_cleanup(curl);
    }
    curl_global_cleanup();
  } else {
    waitpid(pid, NULL, 0);
  }
  return 0;
}

@stevenharman
Copy link

I think it's not just spring -- I'm starting to see this in my dev environment. I can't quite pin it down, might be something in sidekiq.

Yes, it's not just Spring - it's anything that uses fork while also having already spawned threads. For example Puma's clustered mode, Sidekiq Enterprise' swarm (multi-process) feature, etc…

@bmulholland
Copy link

@stevenharman That article is from 2017, and references an issue in Ruby that was fixed all the way back in Ruby 2.4. I'm guessing this is now something else?

@jasonbosco
Copy link

If you're on an M1, this seems to work: typhoeus/typhoeus#687 (comment)

@Samsinite
Copy link

Better solution than hacking around the issue with ENV flags, is to call Ethon::Curl.init before the application forks (such as in a Rails initializer), since as of Curl v8.2.0, macOS specific calls were moved into the global init.

@Samsinite
Copy link

Samsinite commented May 1, 2024

I tried just calling Ethon::Curl.init prior to the fork, which didn't resolve the issue.

This now works as of Curl v8.2.0 unless I'm mistaken, I believe the issue was fixed in this commit.

@semaperepelitsa
Copy link
Contributor

I can no longer reproduce the crash myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants