{"id":22205264,"url":"https://github.com/y10k/riser","last_synced_at":"2026-05-01T10:32:23.999Z","repository":{"id":56892364,"uuid":"165474254","full_name":"y10k/riser","owner":"y10k","description":"RISER is a library of  Ruby Infrastructure for cooperative multi-thread/multi-process SERver","archived":false,"fork":false,"pushed_at":"2020-11-01T09:08:02.000Z","size":244,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-01T01:39:23.927Z","etag":null,"topics":["gem","library","multi-process","multi-thread","ruby","rubygem","server"],"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/y10k.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-13T06:38:09.000Z","updated_at":"2020-11-01T09:08:04.000Z","dependencies_parsed_at":"2022-08-21T01:20:34.760Z","dependency_job_id":null,"html_url":"https://github.com/y10k/riser","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/y10k/riser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/y10k%2Friser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/y10k%2Friser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/y10k%2Friser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/y10k%2Friser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/y10k","download_url":"https://codeload.github.com/y10k/riser/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/y10k%2Friser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32494270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["gem","library","multi-process","multi-thread","ruby","rubygem","server"],"created_at":"2024-12-02T17:29:26.761Z","updated_at":"2026-05-01T10:32:23.977Z","avatar_url":"https://github.com/y10k.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"Riser\n=====\n\n**RISER** is a library of **R**uby **I**nfrastructure for cooperative\nmulti-thread/multi-process **SER**ver.\n\nThis library is useful for the following.\n\n* To make a server with tcp/ip or unix domain socket\n* To select a method to execute server from:\n    - Single process multi-thread\n    - Preforked multi-process multi-thread\n* To make a daemon that will be controlled by signal(2)s\n* To separate the object not divided into multiple processes from\n  server process(es) into backend service process\n\nThis library supposes that the user is familiar with the unix process\nmodel and socket programming.\n\nInstallation\n------------\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'riser'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install riser\n\nUsage\n-----\n\n### Simple Server Example\n\nAn example of a simple server is as follows.\n\n```ruby\nrequire 'riser'\nrequire 'socket'\n\nserver = Riser::SocketServer.new\nserver.dispatch{|socket|\n  while (line = socket.gets)\n    socket.write(line)\n  end\n}\n\nserver_socket = TCPServer.new('localhost', 5000)\nserver.start(server_socket)\n```\n\nThis simple server is an echo server that accepts connections at port\nnumber 5000 on localhost and returns the input line as is.  The object\nof `Riser::SocketServer` is the core of riser.  What this example does\nis as follows.\n\n1. Create a new server object of `Riser::SocketServer`.\n2. Register `dispatch` callback to the server object.\n3. Open a tcp/ip server socket.\n4. Pass the server socket to the server object and `start` the server.\n\nIn this example tcp/ip socket is used, but the server will also work\nwith unix domain socket.  By rewriting the `dispatch` callback you can\nmake the server do what you want.  Although this example is\nsimplified, error handling is actually required.  If an exception is\nthrown to outside of the `dispatch` callback, the server stops, so it\nshould be avoided.  See the\n'[halo.rb](https://github.com/y10k/riser/blob/master/example/halo.rb)'\nexample for practical code.\n\nBy default, the server performs `dispatch` callback on multi-thread.\nOn Linux, you can see that running the server with 4 threads\n(`{ruby}`) by executing `pstree` command.\n\n```\n$ pstree -ap\n...\n  |   `-bash,23355\n  |       `-ruby,30674 simple_server.rb\n  |           |-{ruby},30675\n  |           |-{ruby},30676\n  |           |-{ruby},30677\n  |           `-{ruby},30678\n...\n```\n\n### Server Attributes\n\nThe server has attributes, and setting attributes changes the behavior\nof the server.  Let's take an example `process_num` attribute.  The\n`process_num` attribute is `0` by default, but by setting it will run\nthe server with multi-process.\n\nIn the following example, `process_num` is set to `2`.\nOthers are the same as simple server example.\n\n```ruby\nrequire 'riser'\nrequire 'socket'\n\nserver = Riser::SocketServer.new\nserver.process_num = 2\nserver.dispatch{|socket|\n  while (line = socket.gets)\n    socket.write(line)\n  end\n}\n\nserver_socket = TCPServer.new('localhost', 5000)\nserver.start(server_socket)\n```\n\nRunning the example will hardly change the appearance, but the server\nis running with multi-process.  You can see that the server is running\nin multiple processes with `pstree` command.\n\n```\n$ pstree -ap\n...\n  |   `-bash,23355\n  |       `-ruby,31283 multiproc_server.rb\n  |           |-ruby,31284 multiproc_server.rb\n  |           |   |-{ruby},31285\n  |           |   |-{ruby},31286\n  |           |   |-{ruby},31287\n  |           |   `-{ruby},31288\n  |           |-ruby,31289 multiproc_server.rb\n  |           |   |-{ruby},31292\n  |           |   |-{ruby},31293\n  |           |   |-{ruby},31294\n  |           |   `-{ruby},31295\n  |           |-{ruby},31290\n  |           `-{ruby},31291\n...\n```\n\nThere are 2 child processes (`|-ruby`) under the parent process\n(`` `-ruby``) having 2 threads , and 4 threads are running for each\nchild process.  The architecture of riser's multi-process server is\nthe passing file descriptors between parent-child processes.  The\nparent process accepts the connection and passes it to threads, each\nthread passes the connection to the child process, and `dispatch`\ncallback is performed in the thread of each child process.\n\nIn addition to `process_num`, the server object has various attributes\nsuch as `thread_num`.  See the source code\n([server.rb: Riser::SocketServer](https://github.com/y10k/riser/blob/master/lib/riser/server.rb))\nfor details of other attributes.\n\n### Daemon\n\nRiser provids the function to daemonize server.  By daemonizing the\nserver, the server will be able to receive signal(2)s and restart.  An\nexample of a simple daemon is as follows.\n\n```ruby\nrequire 'riser'\n\nRiser::Daemon.start_daemon(daemonize: true,\n                           daemon_name: 'simple_daemon',\n                           status_file: 'simple_daemon.pid',\n                           listen_address: 'localhost:5000'\n                          ) {|server|\n\n  server.dispatch{|socket|\n    while (line = socket.gets)\n      socket.write(line)\n    end\n  }\n}\n```\n\nTo daemonize the server, use the module function of\n`Riser::Daemon.start_daemon`.  The `start_daemon` function takes\nparameters in a hash table and works.  The works of `start_daemon` are\nas follows.\n\n1. Daemonize the server process (`daemonize: true`).\n2. Output syslog(2) identified with `simple_daemon`\n   (`daemon_name: 'simple_daemon'`).\n3. Output process id to the file of `simple_daemon.pid` and lock it\n   exclusively (`status_file: 'simple_daemon.pid'`).\n4. Open the tcp/ip server socket of `localhost:5000`\n   (`listen_address: 'localhost:5000'`).\n5. Create a server object and pass it to the block, and you set up the\n   server object in the block, then `start` the server object.\n\nA command prompt is displayed as soon as you start the daemon, but the\ndaemon runs in the background and logs to syslog(2).  Daemonization is\nthe result of `daemonaize: true`.  If `daemonaize: false` is set, the\nprocess is not daemonized, starts in foreground and logs to standard\noutput.  This is useful for debugging daemon.\n\nLooking at the process of the daemon with `pstree` command is as\nfollows.\n\n```\n$ pstree -ap\ninit,1 ro\n  |-ruby,32187 simple_daemon.rb\n  |   `-ruby,32188 simple_daemon.rb\n  |       |-{ruby},32189\n  |       |-{ruby},32190\n  |       |-{ruby},32191\n  |       `-{ruby},32192\n...\n```\n\nThe daemon process is running as the parent of the server process.\nAnd the daemon process is running independently under the init(8)\nprocess.  The daemon process monitors the server process and restarts\nwhen the server process dies.  Also, the daemon process receives some\nsignal(2)s and stops or restarts the server process, and does other\nthings.\n\n### Signal(2)s and Other Daemon Parameters\n\nBy default, the daemon is able to receive the following signal(2)s.\n\n|signal(2)|daemon's action                       |`start_daemon` parameter   |\n|---------|--------------------------------------|---------------------------|\n|`TERM`   |stop server gracefully                |`signal_stop_graceful`     |\n|`INT`    |stop server forcedly                  |`signal_stop_forced`       |\n|`HUP`    |restart server gracefully             |`signal_restart_graceful`  |\n|`QUIT`   |restart server forcedly               |`signal_restart_forced`    |\n|`USR1`   |get queue stat and reset queue stat   |`signal_stat_get_and_reset`|\n|`USR2`   |get queue stat and no reset queue stat|`signal_stat_get_no_reset` |\n|`WINCH`  |stop queue stat                       |`signal_stat_stop`         |\n\nBy setting the parameters of the `start_daemon`, you can change the\nsignal(2) which triggers the action.  Setting the parameter to `nil`\nwill disable the action.  'Queue stat' is explained later.\n\nThe `start_daemon` has other parameters.  See the source code\n([daemon.rb: Riser::Daemon::DEFAULT](https://github.com/y10k/riser/blob/master/lib/riser/daemon.rb))\nfor details of other parameters.\n\n### Server Callbacks\n\nThe server object is able to register callbacks other than `dispatch`.\nThe list of server objects' callbacks is as follows.\n\n|callback                                         |description                                                                                            |\n|-------------------------------------------------|-------------------------------------------------------------------------------------------------------|\n|\u003ccode\u003ebefore_start{\u0026#124;server_socket\u0026#124; ...}\u003c/code\u003e|performed before starting the server. in a multi-process server, it is performed in the parent process.|\n|`at_fork{ ... }`                                 |performed after fork(2)ing on the multi-process server. it is performed in the child process.          |\n|\u003ccode\u003eat_stop{\u0026#124;stop_state\u0026#124; ... }\u003c/code\u003e|performed when a stop signal(2) is received. in a multi-process server, it is performed in the child process.|\n|\u003ccode\u003eat_stat{\u0026#124;stat_info\u0026#124; ... }\u003c/code\u003e |performed when 'get stat' signal(2) is received.                                                       |\n|`preprocess{ ... }`                              |performed before starting 'dispatch loop'. in a multi-process server, it is performed in the child process.|\n|`postprocess{ ... }`                             |performed after 'dispatch loop' is finished. in a multi-process server, it is performed in the child process.|\n|`after_stop{ ... }`                              |performed after the server stop. in a multi-process server, it is performed in the parent process.     |\n|\u003ccode\u003edispatch{\u0026#124;socket\u0026#124; ... }\u003c/code\u003e   |known dispatch callback. in a multi-process server, it is performed in the child process.              |\n\nIt seems necessary to explain the `at_stat` callback.  Riser uses\nqueues to distribute connections to threads and processes, and it is\npossible to get statistics information on queues.  With `USR1` and\n`USR2` signal(2)s, you can start collecting queue statistics\ninformation and get it.  At that time the `at_stat` callback is called\nand used to write queue statistics informations to log etc.  With the\n`WINCH` signal(2), you can stop collecting queue statistics\ninformation.\n\nFor a example of how to use callbacks, see the source code of the\n'[halo.rb](https://github.com/y10k/riser/blob/master/example/halo.rb)'\nexample.\n\n### Server Utilities\n\nRiser provides some useful utilities to  write a server.\n\n|utility                   |description              |\n|--------------------------|-------------------------|\n|`Riser::ReadPoll`         |monitor I/O timeout.     |\n|`Riser::WriteBufferStream`|buffer I/O writes.       |\n|`Riser::LoggingStream`    |log I/O read / write.    |\n\nFor a example of how to use utilities, see the source code of the\n'[halo.rb](https://github.com/y10k/riser/blob/master/example/halo.rb)'\nexample.  Also utilities are simple, so check the source codes of\n'[poll.rb](https://github.com/y10k/riser/blob/master/lib/riser/poll.rb)'\nand\n'[stream.rb](https://github.com/y10k/riser/blob/master/lib/riser/stream.rb)'.\n\n### TLS Server\n\nWith OpenSSL, the riser is able to provide a TLS server.  To provide a\nTLS server you need a certificate and a private key.  An example of a\nsimple TLS server is as follows.\n\n```ruby\nrequire 'openssl'\nrequire 'riser'\n\ncert_path = ARGV.shift or abort('need for server certificate file')\npkey_path = ARGV.shift or abort('need for server private key file')\n\nRiser::Daemon.start_daemon(daemonize: false,\n                           daemon_name: 'simple_tls',\n                           listen_address: 'localhost:5000'\n                          ) {|server|\n\n  ssl_context = OpenSSL::SSL::SSLContext.new\n  ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(cert_path))\n  ssl_context.key = OpenSSL::PKey.read(File.read(pkey_path))\n\n  server.dispatch{|socket|\n    ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)\n    ssl_socket.accept\n    while (line = ssl_socket.gets)\n      ssl_socket.write(line)\n    end\n    ssl_socket.close\n  }\n}\n```\n\nAn example of the result of connecting to the TLS server from OpenSSL\nclient is as follows.\n\n```\n$ openssl s_client -CAfile local_ca.cert -connect localhost:5000\nCONNECTED(00000003)\ndepth=1 C = JP, ST = Tokyo, L = Tokyo, O = Private, OU = Home, CN = *\nverify return:1\ndepth=0 C = JP, ST = Tokyo, L = Tokyo, O = Private, OU = Home, CN = localhost\nverify return:1\n---\nCertificate chain\n 0 s:/C=JP/ST=Tokyo/L=Tokyo/O=Private/OU=Home/CN=localhost\n   i:/C=JP/ST=Tokyo/L=Tokyo/O=Private/OU=Home/CN=*\n---\nServer certificate\n-----BEGIN CERTIFICATE-----\nMIIDODCCAiACCQCks7GdVjzAmDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJK\nUDEOMAwGA1UECAwFVG9reW8xDjAMBgNVBAcMBVRva3lvMRAwDgYDVQQKDAdQcml2\nYXRlMQ0wCwYDVQQLDARIb21lMQowCAYDVQQDDAEqMB4XDTE5MDExNTA4MzYzMloX\nDTI5MDExMjA4MzYzMlowYjELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMQ4w\nDAYDVQQHDAVUb2t5bzEQMA4GA1UECgwHUHJpdmF0ZTENMAsGA1UECwwESG9tZTES\nMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAvsrEIm1Unna7KM4U45ibGG/A4pnEScMymaLoitbVr5wAzvn/Oj2UkRO0gzQl\ntLh28+jKh1eIlg60jyJ+QqpRCDWXXkEKXaAETbpYK1dlGE3ORI1VdTe/tYlpFxdd\nBzq//pQVNnYw6I+eu+VNIGroI7rWybsvpwPXgqaiyFlmrP9i8VdZKvKketc+NNwt\nChf81NJ9I1ue0cFZz+bMI84xhulVfxPi1avoXy0Ai+FM4Zqao5dkkKbmgia6R34e\nJ9P7FIGYHypj988fRVs2Pqprh60Zx32oJsLRZzgeiIUqkim3fWDs0TydxAuG6Owl\nXgyCsdTGvwPM9ZQJQgczJsJCNwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCt0lUl\nX1b+r7xAnnBdmxYfIkEoMeEhe5VUB+/Onixb8C3sIdzM8PdXo43OKe/lb9kKY7Gz\nJQMFgrD4jc53mygU4K5gBXKZYOC3/NDNyqSr+22VHMqSD/pImjVFZ9E69gyqVXJ5\nmQBUWgUU4QhpgMnOi0HsN1bpjTiHEaCo7ODlNtF3fhj0bC5CzofxnNMUjTJAn8Rh\nA+fj/6dtDP+lMX//QkjtHOdVafKN8BJRrZg/DliGrqpUKW8h3NxCjGLeG5rFnVVj\nqPFc7IbH25KMLMDCJ3xrqBVtOOEjdTFKbfqOo58HZD7f/PYdQ0XHpG+/f6s+TgTl\nL+yNZF+/WlW7/020\n-----END CERTIFICATE-----\nsubject=/C=JP/ST=Tokyo/L=Tokyo/O=Private/OU=Home/CN=localhost\nissuer=/C=JP/ST=Tokyo/L=Tokyo/O=Private/OU=Home/CN=*\n---\nNo client certificate CA names sent\nPeer signing digest: SHA512\nServer Temp Key: X25519, 253 bits\n---\nSSL handshake has read 1453 bytes and written 269 bytes\nVerification: OK\n---\nNew, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384\nServer public key is 2048 bit\nSecure Renegotiation IS supported\nCompression: NONE\nExpansion: NONE\nNo ALPN negotiated\nSSL-Session:\n    Protocol  : TLSv1.2\n    Cipher    : ECDHE-RSA-AES256-GCM-SHA384\n    Session-ID: 43427207A2C36F807AF5BDCB69EF26F18758A5BAC5C4867C04B17E1C1F6CAE9D\n    Session-ID-ctx:\n    Master-Key: 7BE6C8E0108A6A2F9B2B6AC4DB8360EE375A950D2EB4CB2B259125FB17BE74F00120F7E7290B7137E16F665F44D8AD20\n    PSK identity: None\n    PSK identity hint: None\n    SRP username: None\n    TLS session ticket lifetime hint: 7200 (seconds)\n    TLS session ticket:\n    0000 - af bd 28 5e ca d4 ae 61-38 63 ff 68 84 2b 51 13   ..(^...a8c.h.+Q.\n    0010 - d3 c5 7f c7 72 be f5 5c-bd 9e fb f3 88 61 83 01   ....r..\\.....a..\n    0020 - a5 11 fe 45 14 c1 9c 9b-79 7b 34 87 c1 66 e1 cd   ...E....y{4..f..\n    0030 - 7d f4 ac 62 6e 25 53 c5-35 b2 b2 2c 3c b9 af 89   }..bn%S.5..,\u003c...\n    0040 - cf 11 1d 9c 42 5a 75 86-d1 6d 49 fc e9 6a 39 f0   ....BZu..mI..j9.\n    0050 - fb cf 7d 9a 60 52 10 ad-a3 15 1b ba 00 32 67 e8   ..}.`R.......2g.\n    0060 - 03 ea 74 49 17 46 d8 a2-41 45 17 9d 2c ec 7f 3f   ..tI.F..AE..,..?\n    0070 - 89 eb 7e 4a 05 10 a3 81-d2 16 ce c7 da 7d c6 5a   ..~J.........}.Z\n    0080 - 9c 50 de a5 ce 8e ca 58-af 0b 94 d2 2a c2 56 da   .P.....X....*.V.\n    0090 - 00 05 b9 87 3c 9c 0e 53-70 c2 59 24 ef 0b 0a f3   ....\u003c..Sp.Y$....\n\n    Start Time: 1549358604\n    Timeout   : 7200 (sec)\n    Verify return code: 0 (ok)\n    Extended master secret: yes\n---\nfoo\nfoo\nbar\nbar\nDONE\n```\n\n### dRuby Services\n\nRiser has a mechanism that runs the object in a separate process from\nthe server process.  This mechanism pools the dRuby server processes\nand distributes the object to them in the following 3 patterns.\n\n|pattern       |description                                              |\n|--------------|---------------------------------------------------------|\n|any process   |run the object with a randomly picked process.           |\n|single process|always run the object in the same process.               |\n|sticky process|run the object in the same process for each specific key.|\n\nA simple example of how this mechanism works is as follows.\n\n```ruby\nrequire 'riser'\n\nRiser::Daemon.start_daemon(daemonize: false,\n                           daemon_name: 'simple_services',\n                           listen_address: 'localhost:8000'\n                          ) {|server|\n\n  services = Riser::DRbServices.new(4)\n  services.add_any_process_service(:pid_any, proc{ $$ })\n  services.add_single_process_service(:pid_single, proc{ $$ })\n  services.add_sticky_process_service(:pid_stickty, proc{|key| $$ })\n\n  server.process_num = 2\n  server.before_start{|server_socket|\n    services.start_server\n  }\n  server.at_fork{\n    services.detach_server\n  }\n  server.preprocess{\n    services.start_client\n  }\n  server.dispatch{|socket|\n    if (line = socket.gets) then\n      method, uri, _version = line.split\n      while (line = socket.gets)\n        line.strip.empty? and break\n      end\n      if (method == 'GET') then\n        socket \u003c\u003c \"HTTP/1.0 200 OK\\r\\n\"\n        socket \u003c\u003c \"Content-Type: text/plain\\r\\n\"\n        socket \u003c\u003c \"\\r\\n\"\n\n        path, query = uri.split('?', 2)\n        case (path)\n        when '/any'\n          socket \u003c\u003c 'pid: ' \u003c\u003c services.call_service(:pid_any) \u003c\u003c \"\\n\"\n        when '/single'\n          socket \u003c\u003c 'pid: ' \u003c\u003c services.call_service(:pid_single) \u003c\u003c \"\\n\"\n        when '/sticky'\n          key = query || 'default'\n          socket \u003c\u003c 'key: ' \u003c\u003c key \u003c\u003c \"\\n\"\n          socket \u003c\u003c 'pid: ' \u003c\u003c services.call_service(:pid_stickty, key) \u003c\u003c \"\\n\"\n        else\n          socket \u003c\u003c \"unknown path: #{path}\\n\"\n        end\n      end\n    end\n  }\n  server.after_stop{\n    services.stop_server\n  }\n}\n```\n\n`Riser::DRbServices` is the mechanism for distributing objects.  What\nthis example does is as follows.\n\n1. Create a object of `Riser::DRbServices` to pool 4 dRuby server\n   processes (`Riser::DRbServices.new(4)`).\n2. Add objects to run in dRuby server process\n   (`add_..._process_service`).  In this example it is added that the\n   procedures that returns the process id to see the process to be\n   distributed the object.\n3. Start dRuby server process with `start_server`.  In the case of a\n   multi-process server, it is necessary to execute `start_server` in\n   the parent process, so it is executed at `before_start` callback.\n4. In the case of a multi-process server, execute `detach_server` at\n   `at_fork` callback to release unnecessary resources in the child\n   process.\n5. Start dRuby client with `start_client`.  In the case of a\n   multi-process server, it is necessary to execute `start_client` in\n   the child process, so it is executed at `preprocess` callback.\n6. Add the processing of the web service at `dispatch` callback.  The\n   procedures added to `Riser::DRbServices` are able to be called by\n   `call_service`.  For object other than procedure, use\n   `get_service`.\n7. Stop dRuby server process with `stop_server`.  In the case of a\n   multi-process server, it is necessary to execute `stop_server` in\n   the parent process, so it is executed at `after_stop` callback.\n\nIn this example, you can see the dRuby process distribution with a\nsimple web service.  Looking at the process of this example with\n`pstree` command is as follows.\n\n```\n$ pstree -ap\n...\n  |   `-bash,23355\n  |       `-ruby,3177 simple_services.rb\n  |           `-ruby,3178 simple_services.rb\n  |               |-ruby,3179 simple_services.rb\n  |               |   |-{ruby},3180\n  |               |   |-{ruby},3189\n  |               |   `-{ruby},3198\n  |               |-ruby,3181 simple_services.rb\n  |               |   |-{ruby},3182\n  |               |   |-{ruby},3194\n  |               |   `-{ruby},3202\n  |               |-ruby,3183 simple_services.rb\n  |               |   |-{ruby},3184\n  |               |   |-{ruby},3197\n  |               |   `-{ruby},3207\n  |               |-ruby,3185 simple_services.rb\n  |               |   |-{ruby},3186\n  |               |   |-{ruby},3201\n  |               |   `-{ruby},3211\n  |               |-ruby,3187 simple_services.rb\n  |               |   |-{ruby},3188\n  |               |   |-{ruby},3191\n  |               |   |-{ruby},3195\n  |               |   |-{ruby},3199\n  |               |   |-{ruby},3203\n  |               |   |-{ruby},3205\n  |               |   |-{ruby},3206\n  |               |   |-{ruby},3208\n  |               |   `-{ruby},3209\n  |               |-ruby,3190 simple_services.rb\n  |               |   |-{ruby},3196\n  |               |   |-{ruby},3200\n  |               |   |-{ruby},3204\n  |               |   |-{ruby},3210\n  |               |   |-{ruby},3212\n  |               |   |-{ruby},3213\n  |               |   |-{ruby},3214\n  |               |   |-{ruby},3215\n  |               |   `-{ruby},3216\n  |               |-{ruby},3192\n  |               `-{ruby},3193\n...\n```\n\nIn addition to the 2 server child processes with many threads, there\nare 4 child processes.  These 4 child processes are dRuby server\nprocesses.  See the web service's result of 'any process' pattern.\n\n```\n$ curl http://localhost:8000/any\npid: 3181\n$ curl http://localhost:8000/any\npid: 3179\n$ curl http://localhost:8000/any\npid: 3183\n$ curl http://localhost:8000/any\npid: 3181\n```\n\nIn the 'any process' pattern, process ids are dispersed.  Next, see\nthe web service's result of 'single process' pattern.\n\n```\n$ curl http://localhost:8000/single\npid: 3179\n$ curl http://localhost:8000/single\npid: 3179\n$ curl http://localhost:8000/single\npid: 3179\n$ curl http://localhost:8000/single\npid: 3179\n```\n\nIn the 'single process' pattern, process id is always same.  Last, see\nthe web service's result of 'sticky process' pattern.\n\n```\n$ curl http://localhost:8000/sticky\nkey: default\npid: 3181\n$ curl http://localhost:8000/sticky\nkey: default\npid: 3181\n$ curl http://localhost:8000/sticky?foo\nkey: foo\npid: 3179\n$ curl http://localhost:8000/sticky?foo\nkey: foo\npid: 3179\n$ curl http://localhost:8000/sticky?bar\nkey: bar\npid: 3185\n$ curl http://localhost:8000/sticky?bar\nkey: bar\npid: 3185\n```\n\nIn the 'sticky process' pattern, the same process id will be given for\neach key.\n\n### Local Services\n\nSince dRuby's remote process call has overhead, riser is able to\ntransparently switch `Riser::DRbServices` to local process call.  An\nexample of a local process call is as follows.\n\n```ruby\nrequire 'riser'\n\nRiser::Daemon.start_daemon(daemonize: false,\n                           daemon_name: 'local_services',\n                           listen_address: 'localhost:8000'\n                          ) {|server|\n\n  services = Riser::DRbServices.new(0)\n  services.add_any_process_service(:pid_any, proc{ $$ })\n  services.add_single_process_service(:pid_single, proc{ $$ })\n  services.add_sticky_process_service(:pid_stickty, proc{|key| $$ })\n\n  server.process_num = 0\n  server.before_start{|server_socket|\n    services.start_server\n  }\n  server.at_fork{\n    services.detach_server\n  }\n  server.preprocess{\n    services.start_client\n  }\n  server.dispatch{|socket|\n    if (line = socket.gets) then\n      method, uri, _version = line.split\n      while (line = socket.gets)\n        line.strip.empty? and break\n      end\n      if (method == 'GET') then\n        socket \u003c\u003c \"HTTP/1.0 200 OK\\r\\n\"\n        socket \u003c\u003c \"Content-Type: text/plain\\r\\n\"\n        socket \u003c\u003c \"\\r\\n\"\n\n        path, query = uri.split('?', 2)\n        case (path)\n        when '/any'\n          socket \u003c\u003c 'pid: ' \u003c\u003c services.call_service(:pid_any) \u003c\u003c \"\\n\"\n        when '/single'\n          socket \u003c\u003c 'pid: ' \u003c\u003c services.call_service(:pid_single) \u003c\u003c \"\\n\"\n        when '/sticky'\n          key = query || 'default'\n          socket \u003c\u003c 'key: ' \u003c\u003c key \u003c\u003c \"\\n\"\n          socket \u003c\u003c 'pid: ' \u003c\u003c services.call_service(:pid_stickty, key) \u003c\u003c \"\\n\"\n        else\n          socket \u003c\u003c \"unknown path: #{path}\\n\"\n        end\n      end\n    end\n  }\n  server.after_stop{\n    services.stop_server\n  }\n}\n```\n\nThe differences from the previous example is as follows.\n\n|code                       |description                                                                                                  |\n|---------------------------|-------------------------------------------------------------------------------------------------------------|\n|`Riser::DRbServices.new(0)`|setting the number of processes to `0` makes local process call without starting the dRuby server processes. |\n|`server.process_num = 0`   |since local process call fails if it is a multi-process server, set it to single process multi-thread server.|\n\nIn this example, there are no dRuby server processes and there is only\n1 server process.  Looking at the process of this example with\n`pstree` command is as follows.\n\n```\n$ pstree -ap\n...\n  |   `-bash,23355\n  |       `-ruby,3854 local_services.rb\n  |           `-ruby,3855 local_services.rb\n  |               |-{ruby},3856\n  |               |-{ruby},3857\n  |               |-{ruby},3858\n  |               |-{ruby},3859\n  |               `-{ruby},3860\n...\n```\n\nThe result of the web service always returns the same process id.\n\n```\n$ curl http://localhost:8000/any\npid: 3855\n$ curl http://localhost:8000/any\npid: 3855\n$ curl http://localhost:8000/any\npid: 3855\n```\n\n```\n$ curl http://localhost:8000/single\npid: 3855\n$ curl http://localhost:8000/single\npid: 3855\n$ curl http://localhost:8000/single\npid: 3855\n```\n\n```\n$ curl http://localhost:8000/sticky\nkey: default\npid: 3855\n$ curl http://localhost:8000/sticky\nkey: default\npid: 3855\n$ curl http://localhost:8000/sticky?foo\nkey: foo\npid: 3855\n$ curl http://localhost:8000/sticky?foo\nkey: foo\npid: 3855\n$ curl http://localhost:8000/sticky?bar\nkey: bar\npid: 3855\n$ curl http://localhost:8000/sticky?bar\nkey: bar\npid: 3855\n```\n\n### dRuby Services Callbacks\n\nThe object of `Riser::DRbServices` is able to register callbacks.\nThe list of  callbacks is as follows.\n\n|callback                                          |description                                               |\n|--------------------------------------------------|----------------------------------------------------------|\n|\u003ccode\u003eat_fork(service_name) {\u0026#124;service_front\u0026#124; ... }\u003c/code\u003e|performed when dRuby server process starts with remote process call. ignored by local process call.|\n|\u003ccode\u003epreprocess(service_name) {\u0026#124;service_front\u0026#124; ... }\u003c/code\u003e |performed before starting the server.|\n|\u003ccode\u003epostprocess(service_name) {\u0026#124;service_front\u0026#124; ... }\u003c/code\u003e|performed after the server stop.     |\n\n### dRuby Services and Resource\n\nAn example of running a pstore database in a single process without\ncollision in a multi-process server is as follows.\n\n```ruby\nrequire 'pstore'\nrequire 'riser'\n\nRiser::Daemon.start_daemon(daemonize: false,\n                           daemon_name: 'simple_count',\n                           listen_address: 'localhost:8000'\n                          ) {|server|\n\n  services = Riser::DRbServices.new(1)\n  services.add_single_process_service(:pstore, PStore.new('simple_count.pstore', true))\n\n  server.process_num = 2\n  server.before_start{|server_socket|\n    services.start_server\n  }\n  server.at_fork{\n    services.detach_server\n  }\n  server.preprocess{\n    services.start_client\n  }\n  server.dispatch{|socket|\n    if (line = socket.gets) then\n      method, _uri, _version = line.split\n      while (line = socket.gets)\n        line.strip.empty? and break\n      end\n      if (method == 'GET') then\n        socket \u003c\u003c \"HTTP/1.0 200 OK\\r\\n\"\n        socket \u003c\u003c \"Content-Type: text/plain\\r\\n\"\n        socket \u003c\u003c \"\\r\\n\"\n\n        services.get_service(:pstore).transaction do |pstore|\n          pstore[:count] ||= 0\n          pstore[:count] += 1\n          socket \u003c\u003c 'count: ' \u003c\u003c pstore[:count] \u003c\u003c \"\\n\"\n        end\n      end\n    end\n  }\n  server.after_stop{\n    services.stop_server\n  }\n}\n```\n\nThe result of the web service in this example is as follows.\n\n```\n$ curl http://localhost:8000/\ncount: 1\n$ curl http://localhost:8000/\ncount: 2\n$ curl http://localhost:8000/\ncount: 3\n$ ls -l *.pstore\n-rw-r--r-- 1 toki toki 13 Feb  5 15:21 simple_count.pstore\n```\n\nAn example of using an undefined number of pstore is as follows.  By\nusing `Riser::ResourceSet` and sticky process pattern, you can create\na pstore object on access by each key.\n\n```ruby\nrequire 'pstore'\nrequire 'riser'\n\nRiser::Daemon.start_daemon(daemonize: false,\n                           daemon_name: 'simple_key_count',\n                           listen_address: 'localhost:8000'\n                          ) {|server|\n\n  services = Riser::DRbServices.new(4)\n  services.add_sticky_process_service(:pstore,\n                                      Riser::ResourceSet.build{|builder|\n                                        builder.at_create{|key|\n                                          PStore.new(\"simple_key_count-#{key}.pstore\", true)\n                                        }\n                                        builder.at_destroy{|pstore|\n                                          # nothing to do about `pstore'.\n                                        }\n                                      })\n\n  server.process_num = 2\n  server.before_start{|server_socket|\n    services.start_server\n  }\n  server.at_fork{\n    services.detach_server\n  }\n  server.preprocess{\n    services.start_client\n  }\n  server.dispatch{|socket|\n    if (line = socket.gets) then\n      method, uri, _version = line.split\n      while (line = socket.gets)\n        line.strip.empty? and break\n      end\n      if (method == 'GET') then\n        socket \u003c\u003c \"HTTP/1.0 200 OK\\r\\n\"\n        socket \u003c\u003c \"Content-Type: text/plain\\r\\n\"\n        socket \u003c\u003c \"\\r\\n\"\n\n        _path, query = uri.split('?', 2)\n        key = query || 'default'\n        services.call_service(:pstore, key) {|pstore|\n          pstore.transaction do\n            pstore[:count] ||= 0\n            pstore[:count] += 1\n            socket \u003c\u003c 'key: ' \u003c\u003c key \u003c\u003c \"\\n\"\n            socket \u003c\u003c 'count: ' \u003c\u003c pstore[:count] \u003c\u003c \"\\n\"\n          end\n        }\n      end\n    end\n  }\n  server.after_stop{\n    services.stop_server\n  }\n}\n```\n\nThe result of the web service in this example is as follows.\n\n```\n$ curl http://localhost:8000/\nkey: default\ncount: 1\n$ curl http://localhost:8000/\nkey: default\ncount: 2\n$ curl http://localhost:8000/\nkey: default\ncount: 3\n$ curl http://localhost:8000/?foo\nkey: foo\ncount: 1\n$ curl http://localhost:8000/?foo\nkey: foo\ncount: 2\n$ curl http://localhost:8000/?foo\nkey: foo\ncount: 3\n$ curl http://localhost:8000/?bar\nkey: bar\ncount: 1\n$ curl http://localhost:8000/?bar\nkey: bar\ncount: 2\n$ curl http://localhost:8000/?bar\nkey: bar\ncount: 3\n$ ls -l *.pstore\n-rw-r--r-- 1 toki toki 13 Feb  5 16:16 simple_key_count-bar.pstore\n-rw-r--r-- 1 toki toki 13 Feb  5 16:15 simple_key_count-default.pstore\n-rw-r--r-- 1 toki toki 13 Feb  5 16:15 simple_key_count-foo.pstore\n```\n\nDevelopment\n-----------\n\nAfter checking out the repo, run `bin/setup` to install\ndependencies. You can also run `bin/console` for an interactive prompt\nthat will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake\ninstall`. To release a new version, update the version number in\n`version.rb`, and then run `bundle exec rake release`, which will\ncreate a git tag for the version, push git commits and tags, and push\nthe `.gem` file to [rubygems.org](https://rubygems.org).\n\nContributing\n------------\n\nBug reports and pull requests are welcome on GitHub at\n\u003chttps://github.com/y10k/riser\u003e.\n\nLicense\n-------\n\nThe gem is available as open source under the terms of the\n[MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fy10k%2Friser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fy10k%2Friser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fy10k%2Friser/lists"}