{"id":13878152,"url":"https://github.com/bryanp/goru","last_synced_at":"2025-04-06T12:08:33.840Z","repository":{"id":152046384,"uuid":"442852202","full_name":"bryanp/goru","owner":"bryanp","description":"Concurrent routines for Ruby, without threads or fibers.","archived":false,"fork":false,"pushed_at":"2023-12-24T21:06:37.000Z","size":123,"stargazers_count":150,"open_issues_count":0,"forks_count":5,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-01T20:52:04.222Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bryanp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"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}},"created_at":"2021-12-29T18:03:28.000Z","updated_at":"2024-12-31T18:03:33.000Z","dependencies_parsed_at":"2024-03-13T05:28:09.668Z","dependency_job_id":null,"html_url":"https://github.com/bryanp/goru","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryanp%2Fgoru","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryanp%2Fgoru/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryanp%2Fgoru/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bryanp%2Fgoru/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bryanp","download_url":"https://codeload.github.com/bryanp/goru/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247478322,"owners_count":20945266,"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-08-06T08:01:41.181Z","updated_at":"2025-04-06T12:08:33.817Z","avatar_url":"https://github.com/bryanp.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"**Concurrent routines for Ruby.**\n\nGoru is an experimental concurrency library for Ruby.\n\n* **Lightweight:** Goru routines are not backed by fibers or threads. Each routine creates only ~345 bytes of memory overhead.\n* **Explicit:** Goru requires you to describe exactly how a routine behaves. Less magic makes for fewer bugs when writing concurrent programs.\n\nGoru was intended for low-level programs like http servers and not for direct use in user-facing code.\n\n## How It Works\n\nRoutines are defined with initial state and a block that does work and (optionally) updates the state of the routine:\n\n```ruby\n3.times do\n  Goru::Scheduler.go(:running) { |routine|\n    case routine.state\n    when :running\n      routine.update(:sleeping)\n      routine.sleep(rand)\n    when :sleeping\n      puts \"[#{object_id}] woke up at #{Time.now.to_f}\"\n      routine.update(:running)\n    end\n  }\nend\n```\n\nRoutines run concurrently within a reactor, each reactor running in a dedicated thread. Each eligible routine is called\nonce on every tick of the reactor it is scheduled to run in. In the example above, the three routines sleep for a random\ninterval before waking up and printing the current time. Here is some example output:\n\n```\n[1840] woke up at 1677939216.379147\n[1860] woke up at 1677939217.059535\n[1920] woke up at 1677939217.190349\n[1860] woke up at 1677939217.6196458\n[1920] woke up at 1677939217.935916\n[1840] woke up at 1677939218.033243\n[1860] woke up at 1677939218.532908\n[1920] woke up at 1677939218.8669178\n[1840] woke up at 1677939219.379714\n[1860] woke up at 1677939219.522777\n[1920] woke up at 1677939220.0475688\n[1840] woke up at 1677939220.253979\n```\n\nEach reactor can only run one routine at any given point in time, but if a routine blocks (e.g. by sleeping or\nperforming i/o) the reactor calls another eligible routine before returning to the previously blocked routine\non the next tick.\n\n## Scheduler\n\nBy default Goru routines are scheduled in a global scheduler that waits at the end of the program for all routines\nto finish. While this is useful for small scripts, most use-cases will involve creating your own scheduler and\nregistering routines directly:\n\n```ruby\nscheduler = Goru::Scheduler.new\nscheduler.go { |routine|\n  ...\n}\nscheduler.wait\n```\n\nRoutines are scheduled to run immediately after registration.\n\n### Tuning\n\nSchedulers default to running a number of reactors matching the number of processors on the current system. Tune\nthis to your needs with the `count` option when creating a scheduler:\n\n```ruby\nscheduler = Goru::Scheduler.new(count: 3)\n```\n\n## State\n\nRoutines are initialized with default state that is useful for coordination between ticks. This is perhaps the\noddest part of Goru but the explicitness can make it easier to understand exactly how your routines will behave.\n\nTake a look at the [examples](./examples) to get some ideas.\n\n## Finishing\n\nRoutines will run forever until you say they are finished:\n\n```ruby\nGoru::Scheduler.go { |routine|\n  routine.finished\n}\n```\n\n### Results\n\nWhen finishing a routine you can provide a final result:\n\n```ruby\nroutines = []\nscheduler = Goru::Scheduler.new\nroutines \u003c\u003c scheduler.go { |routine| routine.finished(true) }\nroutines \u003c\u003c scheduler.go { |routine| routine.finished(false) }\nroutines \u003c\u003c scheduler.go { |routine| routine.finished(true) }\nscheduler.wait\n\npp routines.map(\u0026:result)\n# [true, false, true]\n```\n\n## Error Handling\n\nUnhandled errors within a routine cause the routine to enter an `:errored` state. Calling `result` on an errored\nroutine causes the error to be re-raised. Routines can handle errors elegantly using the `handle` method:\n\n```ruby\nGoru::Scheduler.go { |routine|\n  routine.handle(StandardError) do |event:|\n    # do something with `event`\n  end\n\n  ...\n}\n```\n\n## Sleeping\n\nGoru implements a non-blocking version of `sleep` that makes the routine ineligible to be called until the sleep time\nhas elapsed. It is important to note that Ruby's built-in sleep method will block the reactor and should not be used.\n\n```ruby\nGoru::Scheduler.go { |routine|\n  routine.sleep(3)\n}\n```\n\nUnlike `Kernel#sleep` Goru's sleep method requires a duration.\n\n## Channels\n\nGoru offers buffered reading and writing through channels:\n\n```ruby\nchannel = Goru::Channel.new\n\nGoru::Scheduler.go(channel: channel, intent: :w) { |routine|\n  routine \u003c\u003c SecureRandom.hex\n}\n\n# This routine is not invoked unless the channel contains data for reading.\n#\nGoru::Scheduler.go(channel: channel, intent: :r) { |routine|\n  value = routine.read\n}\n```\n\nChannels are unbounded by default, meaning they can hold an unlimited amount of data. This behavior can be changed by\ninitializing a channel with a specific size. Routines with the intent to write will not be invoked unless the channel\nhas space available for writing.\n\n```ruby\nchannel = Goru::Channel.new(size: 3)\n\n# This routine is not invoked if the channel is full.\n#\nGoru::Scheduler.go(channel: channel, intent: :w) { |routine|\n  routine \u003c\u003c SecureRandom.hex\n}\n```\n\n## IO\n\nGoru includes a pattern for non-blocking io. With it you can implement non-blocking servers, clients, etc.\n\nRoutines that involve io must be created with an io object and an intent. Possible intents include:\n\n* `:r` for reading\n* `:w` for writing\n* `:rw` for reading and writing\n\nHere is the beginning of an http server in Goru:\n\n```ruby\nGoru::Scheduler.go(io: TCPServer.new(\"localhost\", 4242), intent: :r) { |server_routine|\n  next unless client = server_routine.accept\n\n  Goru::Scheduler.go(io: client, intent: :r) { |client_routine|\n    next unless data = client_routine.read(16384)\n\n    # do something with `data`\n  }\n}\n```\n\n### Changing Intents\n\nIntents can be changed after a routine is created, e.g. to switch a routine from reading to writing:\n\n```ruby\nGoru::Scheduler.go(io: io, intent: :r) { |routine|\n  routine.intent = :w\n}\n```\n\n## Bridges\n\nGoru supports coordinated buffered io using bridges:\n\n```ruby\nwriter = Goru::Channel.new\n\nGoru::Scheduler.go(io: io, intent: :r) { |routine|\n  case routine.intent\n  when :r\n    routine.bridge(intent: :w, channel: writer) { |bridge|\n      bridge \u003c\u003c SecureRandom.hex\n    }\n  when :w\n    if (data = writer.read)\n      routine.write(data)\n    end\n  end\n}\n```\n\nUsing bridges, the io routine is only called again when two conditions are met:\n\n1. The io object matches the bridged intent (e.g. it is writable).\n2. The channel is in the correct state to reciprocate the intent (e.g. it has data).\n\nSee the [server example](./examples/server.rb) for a more complete use-case.\n\n## Credits\n\nGoru was designed while writing a project in Go and imagining what Go-like concurrency might look like in Ruby.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbryanp%2Fgoru","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbryanp%2Fgoru","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbryanp%2Fgoru/lists"}