{"id":18713347,"url":"https://github.com/jeremyevans/by","last_synced_at":"2025-04-04T07:07:40.082Z","repository":{"id":65743344,"uuid":"598354910","full_name":"jeremyevans/by","owner":"jeremyevans","description":"Ruby Library Preloader","archived":false,"fork":false,"pushed_at":"2024-12-28T04:15:46.000Z","size":37,"stargazers_count":111,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-28T06:09:04.165Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jeremyevans.png","metadata":{"files":{"readme":"README.rdoc","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-06T23:44:59.000Z","updated_at":"2025-02-27T15:55:24.000Z","dependencies_parsed_at":"2024-11-13T18:18:37.851Z","dependency_job_id":"26c00000-167a-4757-83ae-6d206c7ef9b1","html_url":"https://github.com/jeremyevans/by","commit_stats":{"total_commits":18,"total_committers":3,"mean_commits":6.0,"dds":"0.11111111111111116","last_synced_commit":"bbfec28460f7a52a2927ce34babf19f063e271b2"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyevans%2Fby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyevans%2Fby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyevans%2Fby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremyevans%2Fby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeremyevans","download_url":"https://codeload.github.com/jeremyevans/by/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247135144,"owners_count":20889421,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T12:48:10.042Z","updated_at":"2025-04-04T07:07:40.063Z","avatar_url":"https://github.com/jeremyevans.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"= by\n\nby is a library preloader for Ruby designed to speed up process startup.\nIt uses a client/server approach, where the server loads the libraries and\nlistens on a UNIX socket, and the client connects to that socket to run\na process.  For each client connection, the server forks a worker process,\nwhich uses the current directory, stdin, stdout, stderr, and environment\nof the client process.  The worker process then processes the arguments\nprovided by the client. The client process waits until the worker process\nreturns an exit code and closes the socket, and uses exit code 0 (normal\nexit) if the worker process indicates success, or exit code 1 (error)\nif the worker process indicates an error.\n\n== Installation\n\n  gem install by\n\n== Source Code\n\nSource code is available on GitHub at https://github.com/jeremyevans/by\n\n== Usage\n\nTo use +by+, you first start \u003ctt\u003eby-server\u003c/tt\u003e, passing in libraries you would\nlike to preload.\n\n  $ by-server sequel roda capybara\n\nThen you can run ruby with the libraries preloaded using +by+:\n\n  $ by -e 'p [Sequel, Roda, Capybara]'\n  [Sequel, Roda, Capybara]\n\nThe advantage of using +by+ is that the libraries are already loaded,\nso Ruby doesn't have to find the libraries and parse the files in each\nlibrary on process startup.  Here's a performance comparison:\n\n  $ /usr/bin/time ruby -e 'require \"sequel\"; require \"roda\"; require \"capybara\"'\n          1.67 real         0.93 user         0.66 sys\n\n  $ /usr/bin/time   by -e 'require \"sequel\"; require \"roda\"; require \"capybara\"'\n          0.37 real         0.20 user         0.15 sys\n\nThe more libraries your program uses that you can preload in the server\nprogram, the greater the speedup this offers.\n\n== Speeding Things Up Even More By Avoiding Rubygems\n\nLoading Rubygems is by far the slowest thing that Ruby does during\nprocess initialization:\n\n  $ /usr/bin/time ruby -e ''\n          0.25 real         0.11 user         0.14 sys\n\n  $ /usr/bin/time ruby --disable-gems -e ''\n          0.03 real         0.02 user         0.01 sys\n\nYou can speedup +by+ by making it not require rubygems, since it only\nneeds the +socket+ standard library. The only issue with that is that\n+by+ is distributed as a gem.  There are a few workarounds.\n\n1. Create a shell alias.  How you create the alias will depend on\n   the shell you are using, but here's some Ruby code that will\n   output an alias command that will work for most shells:\n\n     require 'rbconfig'\n     by = Gem.activate_bin_path(\"by\", \"by\")\n     puts \"alias by='#{RbConfig.ruby} --disable-gems #{by}'\"\n\n   Note that one issue with using a shell alias is that it only\n   works when loaded and used by the shell, it won't work if\n   executed by another program.\n\n2. Copy the +by+ program and modify the shebang line to use the\n   path to your +ruby+ binary and \u003ctt\u003e--disable-gems\u003c/tt\u003e.  You can\n   get the path to the +by+ program with the following Ruby code.\n\n     puts Gem.activate_bin_path(\"by\", \"by\")\n\n   You would copy that file to somewhere in your \u003ctt\u003e$PATH\u003c/tt\u003e\n   before where the rubygems wrapper is installed, and then modify\n   the shebang.\n\n3. Add your own shell wrapper program that calls +by+. Here's some\n   example Ruby code that may work, though whether it does depends\n   on your shell.\n\n     require 'rbconfig'\n     by = Gem.activate_bin_path(\"by\", \"by\")\n     File.binwrite(\"by\", \"#!/bin/sh\\nexec #{RbConfig.ruby} --disable-gems #{by} \\\"$@\\\"\\n\")\n     File.chmod(0755, \"by\")\n\nWith each of these approaches, you can get much faster program\nexecution:\n\n  $ /usr/bin/time ./by -e 'require \"sequel\"; require \"roda\"; require \"capybara\"'\n          0.08 real         0.05 user         0.03 sys\n\nAs you can see, by avoiding Rubygems, using +by+ to require the\nthree libraries executes three times faster than Ruby itself starts\nif you are using Rubygems.\n\nWith each of these approaches, you need to update the alias/wrapper\nany time you update the +by+ gem when the +by+ program itself has\nchanged.  However, the +by+ program itself is quite small and simple\nand unlikely to change.\n\n== Argument Handling\n\n\u003ctt\u003eby-server\u003c/tt\u003e treats all arguments provided on the command line as\narguments to \u003ctt\u003eKernel#require\u003c/tt\u003e.\n\n+by+ passes all arguments to the worker process over the UNIX socket.\n\nThe worker process handles arguments passed by the client in the following\nway:\n\n* If first argument is +m+ or matches \u003ctt\u003e/\\.rb:\\d+\\z\u003c/tt\u003e, uses the +m+\n  gem to run a single minitest test by line number, waiting until after\n  the test is run so that it can return the correct exit code.\n* If first argument is +irb+, starts an IRB shell with remaining arguments\n  in ARGV.\n* If first argument is \u003ctt\u003e-e\u003c/tt\u003e, evaluates second argument as Ruby code,\n  with remaining arguments in ARGV.\n* If no arguments are given, evaluates Ruby code provided on stdin.\n* Otherwise, treats first argument as a file name, expands the file path,\n  and then requires that.  If Minitest is loaded and set to autorun, waits\n  until after Minitest runs tests, so it can return the correct exit code.\n  If Minitest is not loaded or not set to autorun, exits after the file\n  is required.\n\n=== Restarting the Server\n\nIf \u003ctt\u003eby-server\u003c/tt\u003e is already running, running \u003ctt\u003eby-server\u003c/tt\u003e will\nshutdown the existing server and start a new server with the arguments it\nis given.\n\n=== Stopping the Server\n\nRunning \u003ctt\u003eby-server stop\u003c/tt\u003e will stop an existing server without starting\na new server.  If no server is running, \u003ctt\u003eby-server stop\u003c/tt\u003e will exit\nwithout doing anything.\n\nYou can also send a +TERM+ signal to the \u003ctt\u003eby-server\u003c/tt\u003e process to shut\nthe server down gracefully.  Be aware that by default, \u003ctt\u003eby-server\u003c/tt\u003e\ndaemonizes, so the pid of the started \u003ctt\u003eby-server\u003c/tt\u003e will not be the\npid \u003ctt\u003eby-server\u003c/tt\u003e uses to run.  For that reason, it is recommended to\nuse \u003ctt\u003eby-server stop\u003c/tt\u003e to stop the server.\n\n=== Running Multiple Servers\n\n==== Manually\n\nYou can run multiple +by-server+ processes concurrently by making sure\nthey each use a separate UNIX socket, which you can configure with the\n+BY_SOCKET+ environment variable:\n\n  $ BY_SOCKET=~/.by_sequel_socket by-server sequel\n  $ BY_SOCKET=~/.by_roda_socket by-server roda\n  $ BY_SOCKET=~/.by_sequel_socket by -e 'p [defined?(Sequel), defined?(Roda)]'\n  [\"constant\", nil]\n  $ BY_SOCKET=~/.by_roda_socket by -e 'p [defined?(Sequel), defined?(Roda)]'\n  [nil, \"constant\"]\n\n==== Using \u003ctt\u003eby-session\u003c/tt\u003e\n\nIn many cases, it can be helpful to have a separate server process for\neach application directory. \u003ctt\u003eby-session\u003c/tt\u003e exists to make this easier.\n\u003ctt\u003eby-session\u003c/tt\u003e will call \u003ctt\u003eby-server\u003c/tt\u003e with the arguments it is\ngiven, using a socket in the current directory by default, and then open a\nnew shell.  When the shell exits, \u003ctt\u003eby-session\u003c/tt\u003e will stop the\n\u003ctt\u003eby-server\u003c/tt\u003e it spawned.\n\nIf the directory in which you are running \u003ctt\u003eby-session\u003c/tt\u003e has a +Gemfile+,\nyou could add a file named \u003ctt\u003e.by-session-setup.rb\u003c/tt\u003e in your home directory,\nwhich contains:\n\n  require 'bundler/setup'\n  Bundler.require(:default)\n\nWhen you to startup a \u003ctt\u003eby-session\u003c/tt\u003e shell for the directory using the\n+Gemfile+, you can use:\n\n  $ by-session ~/.by-session-setup\n\nThis will load all gems in the +Gemfile+ into the \u003ctt\u003eby-server\u003c/tt\u003e process.\nIf you are doing this, you must be careful to only run this in a directory\nthat you trust.\n\nIf you don't want to specify the \u003ctt\u003e~/.by-session-setup\u003c/tt\u003e argument every\ntime you start \u003ctt\u003eby-session\u003c/tt\u003e, you can use the +BY_SERVER_AUTO_REQUIRE+\nenvironment variable.\n\n=== Environment Variables\n\n+BY_SOCKET+ :: The path to the UNIX socket to listen on (\u003ctt\u003eby-server\u003c/tt\u003e)\n               or connect to (+by+). \n+DEBUG+ :: If set to +log+, logs \u003ctt\u003e$LOADED_FEATURES\u003c/tt\u003e to stdout\n           after requiring libraries (\u003ctt\u003eby-server\u003c/tt\u003e) or before worker\n           process shutdown (+by+).\n\n==== \u003ctt\u003eby-server\u003c/tt\u003e-Specific Environment Variables\n\n+BY_SERVER_AUTO_REQUIRE+ :: Whitespace separated list of libraries for\n                            \u003ctt\u003eby-server\u003c/tt\u003e to require, before it requires\n                            command line arguments.\n+BY_SERVER_NO_DAEMON+ :: Do not daemonize if set.\n+BY_SERVER_DAEMON_NO_CHDIR+ :: Do not change directory to \u003ctt\u003e/\u003c/tt\u003e\n                               when daemonizing if set.\n+BY_SERVER_DAEMON_NO_REDIR_STDIO+ :: Do not redirect stdio to\n                                     \u003ctt\u003e/dev/null\u003c/tt\u003e when daemonizing\n                                     if set.\n\n== \u003ctt\u003eby-server\u003c/tt\u003e Signals\n\n+QUIT+ :: Close the socket (this is what \u003ctt\u003eby-server stop\u003c/tt\u003e uses).\n+TERM+ :: Delete the socket path and then close the socket.\n\n== Internals\n\nThere are two classes, \u003ctt\u003eBy::Server\u003c/tt\u003e and \u003ctt\u003eBy::Worker\u003c/tt\u003e.\n\u003ctt\u003eBy::Server\u003c/tt\u003e listens on the UNIX socket, forking worker\nprocesses for each connection. \u003ctt\u003eBy::Worker\u003c/tt\u003e is run in each\nworker process handling receiving data from the +by+ command line\nprogram.\n\nThe +by+ command line program is self-contained, there is no\nRuby class for the behavior, to make sure startup is as fast as\npossible. \u003ctt\u003eby-session\u003c/tt\u003e is also self-contained.\n\n== Customization\n\nFor custom handling of arguments, you can require \u003ctt\u003eby/server\u003c/tt\u003e\nand use the \u003ctt\u003eBy::Server.with_argument_handler\u003c/tt\u003e method. For example,\nif you wanted to add support for an initial \u003ctt\u003e-I\u003c/tt\u003e option to modify\nthe load path, and then use the standard argument handling:\n\n  require 'by/server'\n\n  By::Server.with_argument_handler do |args|\n    if args[0] == '-I'\n      args.shift\n      $LOAD_PATH.unshift(args.shift)\n    end\n    super(args)\n  end.new.run\n\nNote that if you do this, you are responsible for making sure\nto correctly communicate with the client socket.  Otherwise, it's\npossible the client socket may hang waiting on a response. Please\nreview the default argument handling in \u003ctt\u003elib/by/worker.rb\u003c/tt\u003e\nbefore writing your own argument handler.\n\n== Security\n\nAs with any program that forks without executing, the memory layout\nis shared by the client and the server program, which can lead to\nBlind Return Oriented Programming (BROP) attacks.  You should avoid\nusing +by+ to run a program that deals with any untrusted input.\n+by+ makes a deliberate choice to trade security to make process\nstartup as fast as possible.\n\nThe server socket is set to mode 0600, so it is only readable and\nwritable by the same user.\n\n== Name\n\nThe name +by+ was chosen because it is +ruby+ with the +ru+ preloaded.\n\n== Background\n\n+by+ was created in order to {speed up the running of individual tests\nin my production applications}[https://code.jeremyevans.net/2023-02-14-speeding-up-tests-in-applications-using-sequel-and-roda.html].\n\n== Similar Projects\n\n* Spring: https://github.com/rails/spring\n* Spin: https://github.com/jstorimer/spin\n* Spinoff: https://github.com/bernd/spinoff\n\n== License\n\nMIT\n\n== Author\n\nJeremy Evans \u003ccode@jeremyevans.net\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremyevans%2Fby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeremyevans%2Fby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremyevans%2Fby/lists"}