heroku-buildpack-jemalloc
heroku-buildpack-jemalloc copied to clipboard
LD_PRELOAD Causes Error on Deployment
I am investing using this buildpack.
Durning my deployment to heroku, the build is rejected do to an error (cannot open shared object file).
Some of the output (same error over and over) is below.
Any suggestions for a next step? Any other info I could provide?
Thanks, Scott
kickoff:jemal g push staging jemal:master Counting objects: 12, done. Delta compression using up to 8 threads. Compressing objects: 100% (11/11), done. Writing objects: 100% (12/12), 931 bytes | 0 bytes/s, done. Total 12 (delta 9), reused 2 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Deleting 1 files matching .slugignore patterns. remote: -----> jemalloc app detected remote: -----> Vendoring binaries remote: Fetching https://github.com/mojodna/heroku-buildpack-jemalloc/releases/download/4.2.1/jemalloc-4.2.1-1.tar.gz remote: -----> Configuring build environment remote: -----> Building runtime environment remote: -----> Ruby app detected remote: -----> Compiling Ruby/Rails remote: -----> Using Ruby version: ruby-2.3.3 remote: -----> Installing dependencies using bundler 1.13.6 remote: Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment remote: ERROR: ld.so: object '/app/vendor/jemalloc/lib/libjemalloc.so.1' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
Good catch.
Are you setting LD_PRELOAD via heroku config?
If so, change /app/vendor/jemalloc/lib/libjemalloc.so.1 to /app/vendor/jemalloc/lib/libjemalloc.so (the former doesn't exist, despite what the docs and jemalloc.sh say).
If not, set that instead of using jemalloc.sh.
Actually, I'm wrong. /app/vendor/jemalloc/lib/libjemalloc.so.1 does exist in the tarball. I don't know what's going on here.
Which stack are you using?
@mojodna I am using the cedar-14 stack with Ruby 2.3.3. I had set the LD_PREPOLOAD via Heroku config.
I had originally thought it was a typo as well, but I connected via bash and was able to verify the libjemalloc.so.1 does exist.
Exact commands executed: heroku config:set LD_PRELOAD=/app/vendor/jemalloc/lib/libjemalloc.so.1
heroku config -s | grep LD_PRELOAD LD_PRELOAD='/app/vendor/jemalloc/lib/libjemalloc.so.1'
Please let me know if I can provide any other details/info.
Thanks, Scott
I was able to reproduce the error by running heroku run bash against a newly-created app with LD_PRELOAD set (prior to pushing any commits). That was because the app hadn't been built yet, and the dyno only contained the cedar-14 runtime. After pushing a commit containing an empty index.html, I re-ran heroku run bash and things worked swimmingly.
Try reconnecting to the dyno via bash and run (and report back):
ldd /app/vendor/jemalloc/lib/libjemalloc.so.1
Output should be (and there should be no error when connecting):
~ $ ldd /app/vendor/jemalloc/lib/libjemalloc.so.1
linux-vdso.so.1 => (0x00007ffc5dffb000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f53da66d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f53da2a8000)
/lib64/ld-linux-x86-64.so.2 (0x00007f53daacb000)
I get the following:
~ $ ldd /app/vendor/jemalloc/lib/libjemalloc.so.1 linux-vdso.so.1 => (0x00007ffdb438c000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f652d6b6000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f652d2f1000) /lib64/ld-linux-x86-64.so.2 (0x00007f652db14000)
I did a bit more testing. On a brand new/empty rails project (4.2.7.1), I get the error ERROR: ld.so: object '/app/vendor/jemalloc/lib/libjemalloc.so.1' from LD_PRELOAD cannot be preloaded (cannot open shared object file, but the build is still deployed.
In my app, looking through more of the output I also see a JSON:ParseError (see http://d.pr/n/2bZd)
I tried to play around with some of the gem versions of sprockets (3.7.0) and sprocket-rails (3.2.0), but I have not been able to narrow down the issue.
Thanks, Scott
Can you share access with me so I can do some poking around? mojodna / seth at mojodna dot net
Hi @mojodna I'm having the same issue with a Heroku app :
ERROR: ld.so: object '/app/vendor/jemalloc/lib/libjemalloc.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
I'm using the same buildpack/configuration on Scalingo without any specific issues. If you want, I could add you on the open-source project we run with this lib on Heroku.
Curious about this if it still exists, I am wanting to use this in production on Heroku, rails 5, ruby 2.4.1, I'm hesitant to pull the trigger though.
Let's try and get it fixed if it is an issue.
Can everyone using LD_PRELOAD as an env variable (like in your "config settings" in Heroku or whatever) in this thread please try removing that and just use jemalloc.sh in your Procfile?
i.e.:
web: jemalloc.sh bundle exec puma -C config/puma.rb
I havent tried using it yet, but I could try now. I did have one other question, do you know how I might do this if I'm using pgbouncer? It's bin/start-pgbouncer-stunnel below
web: bin/start-pgbouncer-stunnel bundle exec puma -C config/puma.rb
clock: bundle exec clockwork config/clock.rb
worker: bundle exec que ./config/environment.rb
@9mm I think jemalloc.sh bin/start-pgbouncer-stunnel bundle exec puma -C config/puma.rb should work but I am no bash master.
I ran into this issue a while back setting LD_PRELOAD but just prefixing jemalloc.sh is now working for me.
It works with the script, but it makes the Procfile strongly environment-dependent. This "solution" should be considered only a workaround, not a fix to the original issue problem, IMHO.
I too was unable to get LD_PRELOAD to work. Using jemalloc.sh in the Procfile did work, and reduced my overall memory usage significantly, but increased my swap memory usage. Some days, after nearly a full day's uptime, I'd see ~17MB of swap memory when my total memory usage was under 70%. With the stock allocator, those numbers are more like 2-6MB at 80-85% usage.
I bring it up here in case this sort of misbehavior is the result of having the script prepended only to processes in the Procfile, rather than all commands run on the dyno, making the Procfile option less viable. Has anyone else seen this?
This behavior was consistent on both the Cedar-14 and Heroku-16 stacks, running Rails 5.0 with Ruby 2.4.1 on a Hobby web dyno.
@brian-kephart 17mb of swap is nothing to worry about. If anything, it indicates the allocator is being more efficient because it's grouping memory pages in such a way that the operating system can swap larger portions out of the resident set.
I managed to fix this problem by setting LD_PRELOAD in the script in /.profile.d.
I think that the problem we're having is a result of having the environment variable present before the buildpack scripts pull in the binaries. Setting the variable within the buildpack seems to solve it.
Before I submit a pull request, is there any reason not to set LD_PRELOAD within the buildpack?
@brian-kephart I believe the issue with LD_PRELOAD is that Heroku is taking the user environment while doing the build , which is wrong and can even be considered a security hazard.
I opened a support case on that a long time ago but they didn't provide a solution.
It seems that using LD_PRELOAD now doesn't cause a build error , they seem to ignore the stderr stream containing the error message.
So other than looking ugly in the logs it should be safe.
But I like your solution better , one question , how do you confirm it's actually being used?
heroku run bash / rails console and check LD_PRELOAD value?
@brian-kephart another option , ugly as it may is to wrap your and run:
heroku heroku config:unset LD_PRELOADgit pushheroku heroku config:set LD_PRELOAD /app/vendor/jemalloc/lib/libjemalloc.so
This should remove all build log errors and apply it to the environment in full.
It's just ugly and require 3 restarts instead of 1 , I expected Heroku to do this when they do the build on their side , unset/set special Linux variables like LD_PRELOAD.
I can confirm @ohaddahan's experience with it no longer causing a build error.
I had read @nateberkopec's recent blog post and tried again yesterday. I still see many errors during the deploy, but it is successful.
If there is an easy way to test that it is properly and working/active that would be really helpful.
@scottwater you can log into your machine and check either:
heroku rails console -> File.exists?( ENV['LD_PRELOAD'] )
or
heroku bash -> ls $LD_PRELOAD
If it exists and set, all Linux process must load the shared library before they start running. https://blog.fpmurphy.com/2012/09/all-about-ld_preload.html
@ohaddahan Yes, you can check that the LD_PRELOAD value is correct as you described, but follow mojodna's instructions in this thread from 12/27/16 to make sure the specified file is actually there. If the LD_PRELOAD value is correct, the file is present, and you see no runtime errors, all should be well. There might be better checks in the jemalloc documentation, though.
@brian-kephart basically if ls $LD_PRELOAD run and you don't see any LD_PRELOAD failed ... error (since ls also loaded it) , it's fine.
If you really want to be on the safe side , you can write a small executable that will try loading some symbols from jemalloc and add it to your application slug and than run it from inside Heroku shell. But that's a little over the top :)
Xavier Noria asked me a similar question once - is it possible to be completely sure jemalloc was running in the Ruby process. As you mentioned @ohaddahan, I think trying to use a jemalloc specific feature or symbol would be the only way to be totally sure.
@nateberkopec actually I think it can be simpler , attaching to the ruby process with gdb or running collect / strace / ltrace on the process.
You can also use LD_DEBUG=all to see move info , but all of these seem over the top.
LD_PRELOAD is really vocal about succeeding or not :)
I believe this would be fixed by #18 when using JEMALLOC_ENABLED as LD_PRELOAD is being set in .profile.d/jemalloc so builds shouldn't be using it. Also, in my testing of bin/compile, none of the config variables were set with the expectation that buildpack developers would read any they need from env-dir/VARIABLE (which #18 does for JEMALLOC_VERSION).
how do i verify if jemalloc is running on heroku/dokku (im running RoR)
@wongy91 Here's one way:
$ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']"
-lpthread -ljemalloc -lgmp -ldl -lobjc
@citizen428 I'm not OP, but running $ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']" in heroku run bash yields:
-lpthread -lgmp -ldl -lcrypt -lm
But I can confirm it shows -ljemalloc when running that command in my local environment. For prod, I've prefixed my dynos with jemalloc.sh and also set JEMALLOC_ENABLED=true in my Heroku config and my app runs fine. Additionally, running $ ldd /app/vendor/jemalloc/lib/libjemalloc.so.1 in heroku bash will yield
linux-vdso.so.1 => (0x00007ffeabfeb000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f4adb7e9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4adb41f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4adbc46000)
This is all to ask, am I right in believing that jemalloc is likely running despite not seeing it built when running the command you've provided? I just installed jemalloc onto my server so I'm not entirely sure if I've done everything right.
@k5o you can try this CheckJEMalloc , this actually tries using a symbol from JEMalloc.
@ohaddahan that script is a great idea.
However, like @k5o, my RbConfig::CONFIG['LIBS'] also doesn't contain -ljemalloc so I used that module as a test, and it returned:
irb(main):001:0> CheckJEMalloc.je_malloc_exists?
FFI::NotFoundError : Function 'je_malloc' not found in [/app/vendor/jemalloc/lib/libjemalloc.so.2]
=> false
irb(main):002:0> ENV['LD_PRELOAD']
=> "/app/vendor/jemalloc/lib/libjemalloc.so.2"
irb(main):003:0> puts `file ENV['LD_PRELOAD']`
/app/vendor/jemalloc/lib/libjemalloc.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=78d9b314f3040553f25655f32de403fa5f472f97, with debug_info, not stripped
=> nil
Now, I wonder if that script only works with older jemalloc (I have libjemalloc.so.2 but this discussion seems to only mention libjemalloc.so.1).