{"id":18732330,"url":"https://github.com/hopsoft/local_bus","last_synced_at":"2025-04-05T04:09:32.947Z","repository":{"id":261100824,"uuid":"882766934","full_name":"hopsoft/local_bus","owner":"hopsoft","description":"A lightweight pub/sub system for decoupled intra-process communication in Ruby applications","archived":false,"fork":false,"pushed_at":"2024-12-11T19:24:52.000Z","size":107,"stargazers_count":87,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T13:12:06.922Z","etag":null,"topics":["events","non-blocking-io","publish-subscribe","pubsub","ruby","thread-pool"],"latest_commit_sha":null,"homepage":"","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/hopsoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2024-11-03T17:52:13.000Z","updated_at":"2025-03-18T03:43:16.000Z","dependencies_parsed_at":"2024-11-04T18:21:39.571Z","dependency_job_id":"dee9359b-7344-43ea-b877-9d5b209869a7","html_url":"https://github.com/hopsoft/local_bus","commit_stats":null,"previous_names":["hopsoft/local_bus"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Flocal_bus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Flocal_bus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Flocal_bus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Flocal_bus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hopsoft","download_url":"https://codeload.github.com/hopsoft/local_bus/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247284949,"owners_count":20913704,"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":["events","non-blocking-io","publish-subscribe","pubsub","ruby","thread-pool"],"created_at":"2024-11-07T15:05:22.282Z","updated_at":"2025-04-05T04:09:32.912Z","avatar_url":"https://github.com/hopsoft.png","language":"Ruby","funding_links":["https://github.com/sponsors/hopsoft"],"categories":["Ruby"],"sub_categories":[],"readme":"[![Lines of Code](https://img.shields.io/badge/loc-365-47d299.svg)](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)\n[![GEM Version](https://img.shields.io/gem/v/local_bus)](https://rubygems.org/gems/local_bus)\n[![GEM Downloads](https://img.shields.io/gem/dt/local_bus)](https://rubygems.org/gems/local_bus)\n[![Tests](https://github.com/hopsoft/local_bus/actions/workflows/tests.yml/badge.svg)](https://github.com/hopsoft/local_bus/actions)\n[![Ruby Style](https://img.shields.io/badge/style-standard-168AFE?logo=ruby\u0026logoColor=FE1616)](https://github.com/testdouble/standard)\n[![Sponsors](https://img.shields.io/github/sponsors/hopsoft?color=eb4aaa\u0026logo=GitHub%20Sponsors)](https://github.com/sponsors/hopsoft)\n[![Twitter Follow](https://img.shields.io/twitter/url?label=%40hopsoft\u0026style=social\u0026url=https%3A%2F%2Ftwitter.com%2Fhopsoft)](https://twitter.com/hopsoft)\n\n# LocalBus\n\n### A lightweight single-process pub/sub system that enables clean, decoupled interactions.\n\n\u003e [!TIP]\n\u003e At under 400 lines of code. The LocalBus source can be reviewed quickly to grok its implementation and internals.\n\n## Why LocalBus?\n\nA message bus (or enterprise service bus) is an architectural pattern that enables different parts of an application to communicate without direct knowledge of each other.\nThink of it as a smart postal service for your application - components can send messages to topics, and other components can listen for those messages, all without knowing about each other directly.\n\nEven within a single process, this pattern offers powerful benefits:\n\n- **Decouple Components**: Break complex systems into maintainable parts that can evolve independently\n- **Single Responsibility**: Each component can focus on its core task without handling cross-cutting concerns\n- **Flexible Architecture**: Easily add new features by subscribing to existing events without modifying original code\n- **Control Flow**: Choose immediate or background processing based on your needs\n- **Testing**: Simplified testing as components can be tested in isolation\n- **Stay Reliable**: Built-in error handling and thread safety\n- **Non-Blocking**: Efficient message processing with async I/O\n\n\u003c!-- Tocer[start]: Auto-generated, don't remove. --\u003e\n\n## Table of Contents\n\n  - [Key Benefits](#key-benefits)\n    - [Performance and Efficiency](#performance-and-efficiency)\n    - [Ease of Use](#ease-of-use)\n    - [Decoupling and Modularity](#decoupling-and-modularity)\n    - [Reliability and Safety](#reliability-and-safety)\n  - [Use Cases](#use-cases)\n  - [Key Components](#key-components)\n    - [Bus](#bus)\n    - [Station](#station)\n    - [LocalBus](#localbus)\n  - [Installation](#installation)\n    - [Requirements](#requirements)\n  - [Usage](#usage)\n    - [LocalBus](#localbus-1)\n    - [Bus](#bus-1)\n    - [Station](#station-1)\n  - [Advanced Usage](#advanced-usage)\n    - [Concurrency Controls](#concurrency-controls)\n      - [Bus](#bus-2)\n      - [Station](#station-2)\n        - [Message Priority](#message-priority)\n    - [Error Handling](#error-handling)\n    - [Memory Considerations](#memory-considerations)\n    - [Blocking Operations](#blocking-operations)\n    - [Shutdown \u0026 Cleanup](#shutdown--cleanup)\n    - [Limitations](#limitations)\n    - [Demos \u0026 Benchmarks](#demos--benchmarks)\n  - [See Also](#see-also)\n\n\u003c!-- Tocer[finish]: Auto-generated, don't remove. --\u003e\n\n## Key Benefits\n\nLocalBus offers several advantages that make it an attractive choice for Ruby developers looking to implement a pub/sub system within a single process:\n\n### Performance and Efficiency\n\n- **Non-Blocking I/O:** Leveraging the power of the `Async` library, LocalBus ensures efficient message processing without blocking the main thread, leading to improved performance in I/O-bound applications.\n- **Optimized Resource Usage:** By using semaphores and thread pools, LocalBus efficiently manages system resources, allowing for high concurrency without overwhelming the system.\n\n### Ease of Use\n\n- **Simple Setup:** With straightforward installation and intuitive API, LocalBus allows developers to quickly integrate pub/sub capabilities into their applications.\n- **Minimal Configuration:** Default settings are optimized for most use cases, reducing the need for complex configurations.\n\n### Decoupling and Modularity\n\n- **Component Isolation:** LocalBus enables clean separation of concerns by allowing components to communicate through messages without direct dependencies or tight coupling.\n- **Scalable Architecture:** Easily extend your application by adding new subscribers to existing topics, facilitating the addition of new features without modifying existing code.\n\n### Reliability and Safety\n\n- **Built-in Error Handling:** LocalBus includes error boundaries to ensure that failures in one subscriber do not affect others, maintaining system stability.\n- **Thread Safety:** Designed with concurrency in mind, LocalBus provides thread-safe operations to prevent race conditions and ensure data integrity.\n\n## Use Cases\n\nLocalBus is versatile and can be applied to various scenarios within a Ruby application. Here are some common use cases and examples:\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eDecoupled Communication\u003c/b\u003e\u003c/summary\u003e\n\u003cbr\u003e\nFacilitate communication between different parts of a component-based architecture without tight coupling.\n\n```ruby\n# Component A subscribes to order creation events\nLocalBus.subscribe \"order.created\" do |message|\n  InventoryService.update_stock message.payload[:order_id]\nend\n\n# Component B publishes an order creation event\nLocalBus.publish \"order.created\", order_id: 789\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eReal-Time Notifications\u003c/b\u003e\u003c/summary\u003e\n\u003cbr\u003e\nUse LocalBus to send real-time notifications to users when specific events occur, such as user sign-ups or order completions.\n\n```ruby\n# Subscribe to user sign-up events\nLocalBus.subscribe \"user.signed_up\" do |message|\n  NotificationService.send_welcome_email message.payload[:user_id]\nend\n\n# Publish a user sign-up event\nLocalBus.publish \"user.signed_up\", user_id: 123\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eBackground Processing\u003c/b\u003e\u003c/summary\u003e\n\u003cbr\u003e\nOffload non-critical tasks to be processed in the background, such as sending emails or generating reports.\n\n```ruby\n# Subscribe to report generation requests\nLocalBus.subscribe \"report.generate\" do |message|\n  ReportService.generate message.payload[:report_id]\nend\n\n# Publish a report generation request\nLocalBus.publish \"report.generate\", report_id: 456\n```\n\n\u003c/details\u003e\n\n## Key Components\n\n### Bus\n\nThe Bus acts as a direct transport mechanism for messages, akin to placing a passenger directly onto a bus.\nWhen a message is published to the Bus, it is immediately delivered to all subscribers, ensuring prompt execution of tasks.\nThis is achieved through non-blocking I/O operations, which allow the Bus to handle multiple tasks efficiently without blocking the main thread.\n\n\u003e [!NOTE]\n\u003e While the Bus uses asynchronous operations to optimize performance,\n\u003e the actual processing of a message may still experience slight delays due to I/O wait times from prior messages.\n\u003e This means that while the Bus aims for immediate processing, the nature of asynchronous operations can introduce some latency.\n\n### Station\n\nThe Station serves as a queuing system for messages, similar to a bus station where passengers wait for their bus.\n\nWhen a message is published to the Station, it is queued and processed at a later time, allowing for deferred execution.\nThis is particularly useful for tasks that can be handled later.\n\nThe Station employs a thread pool to manage message processing, enabling high concurrency and efficient resource utilization.\nMessages can also be prioritized, ensuring that higher-priority tasks are processed first.\n\n\u003e [!NOTE]\n\u003e While the Station provides a robust mechanism for background processing,\n\u003e it's important to understand that the exact timing of message processing is not controlled by the publisher,\n\u003e and messages will be processed as resources become available.\n\n### LocalBus\n\nThe LocalBus class serves as the primary interface to the library, providing a convenient singleton pattern for accessing both Bus and Station functionality.\nIt exposes singleton instances of both Bus and Station providing a simplified API for common pub/sub operations.\n\nBy default, LocalBus delegates to the Station singleton for all pub/sub operations, making it ideal for background processing scenarios.\nThis means that when you use `LocalBus.publish` or `LocalBus.subscribe`, you're actually working with default Station, benefiting from its queuing and thread pool capabilities.\n\n## Installation\n\n```bash\nbundle add local_bus\n```\n\n### Requirements\n\n- Ruby `\u003e= 3.0`\n\n## Usage\n\n### LocalBus\n\n```ruby\nLocalBus.subscribe \"user.created\" do |message|\n  # business logic (e.g. API calls, database queries, disk operations, etc.)\n  \"It worked!\"\nend\n\nmessage = LocalBus.publish(\"user.created\", user_id: 123)\nmessage.wait        # blocks until all subscribers complete\nmessage.subscribers # blocks and waits until all subscribers complete and returns the subscribers\n#=\u003e [#\u003cLocalBus::Subscriber:0x0000000120f75c30 ...\u003e]\n\nmessage.subscribers.first.value\n#=\u003e \"It worked!\"\n\n# subscribe with any object that responds to `#call`.\nworker = -\u003e(message) do\n  # business logic (e.g. API calls, database queries, disk operations, etc.)\n  \"It worked!\"\nend\nLocalBus.subscribe \"user.created\", callable: worker\n```\n\n### Bus\n\n```ruby\nbus = LocalBus::Bus.new # ... or LocalBus.instance.bus\n\n# register a subscriber\nbus.subscribe \"user.created\" do |message|\n  # business logic (e.g. API calls, database queries, disk operations, etc.)\n  \"It worked!\"\nend\n\nmessage = bus.publish(\"user.created\", user_id: 123)\nmessage.wait        # blocks until all subscribers complete\nmessage.subscribers # waits and returns the subscribers\n#=\u003e [#\u003cLocalBus::Subscriber:0x000000012bbb79a8 ...\u003e]\n\nmessage.subscribers.first.value\n#=\u003e \"It worked!\"\n```\n\n### Station\n\n```ruby\nstation = LocalBus::Station.new # ... or LocalBus.instance.station\n\nstation.subscribe \"user.created\" do |message|\n  # business logic (e.g. API calls, database queries, disk operations, etc.)\n  \"It worked!\"\nend\n\nmessage = station.publish(\"user.created\", user_id: 123)\nmessage.wait        # blocks until all subscribers complete\nmessage.subscribers # blocks and waits until all subscribers complete and returns the subscribers\n#=\u003e [#\u003cLocalBus::Subscriber:0x00000001253156e8 ...\u003e]\n\nmessage.subscribers.first.value\n#=\u003e \"It worked!\"\n```\n\n## Advanced Usage\n\n### Concurrency Controls\n\n#### Bus\n\nThe Bus leverages Async's Semaphore to limit resource consumption.\nThe configured `concurrency` limits how many operations can run at once.\n\n```ruby\n# Configure concurrency limits for the Bus (default: Etc.nprocessors)\nbus = LocalBus::Bus.new(concurrency: 10)\n```\n\n\u003e [!NOTE]\n\u003e When the max concurrency limit is reached, new publish operations will wait until a slot becomes available.\n\u003e This helps to ensure we don't over utilize system resources.\n\n#### Station\n\nThe Station uses a thread pool for multi-threaded message processing.\nYou can configure the queue size and the number of threads used to process messages.\n\n```ruby\n# Configure the Station\nstation = LocalBus::Station.new(\n  limit: 5_000, # max number of pending messages (default: 10_000)\n  threads: 10,  # max number of processing threads (default: Etc.nprocessors)\n)\n```\n\n##### Message Priority\n\nThe Station supports assigning a priority to each message.\nMessages with a higher priority are processed before lower priority messages.\n\n```ruby\nLocalBus.publish(\"default\")                # 3rd to process\nLocalBus.publish(\"important\", priority: 5) # 2nd to process\nLocalBus.publish(\"critical\", priority: 10) # 1st to process\n```\n\n### Error Handling\n\nError boundaries prevent individual subscriber failures from affecting other subscribers.\n\n```ruby\nLocalBus.subscribe \"user.created\" do |message|\n  raise \"Something went wrong!\"\n  # never reached (business logic...)\nend\n\nLocalBus.subscribe \"user.created\" do |message|\n  # This still executes even though the other subscriber has an error\n  # business logic (e.g. API calls, database queries, disk operations, etc.)\nend\n\n# The publish operation completes with partial success\nmessage = LocalBus.publish(\"user.created\", user_id: 123)\nerrored_subscribers = message.subscribers.select(\u0026:errored?)\n#=\u003e [#\u003cLocalBus::Subscriber:0x000000011ebbcaf0 ...\u003e]\n\nerrored_subscribers.first.error\n#=\u003e #\u003cLocalBus::Subscriber::Error: Invocation failed! Something went wrong!\u003e\n```\n\n\u003e [!IMPORTANT]\n\u003e It's up to you to check message subscribers and handle errors appropriately.\n\n### Memory Considerations\n\nMessages are held in memory until all subscribers have completed.\nConsider this when publishing large payloads or during high load scenarios.\n\n```ruby\n# memory-efficient publishing of large datasets\nlarge_dataset.each_slice(100) do |batch|\n  message = LocalBus.publish(\"data.process\", items: batch)\n  message.wait # wait before processing more messages\nend\n```\n\n### Blocking Operations\n\nLocalBus facilitates non-blocking I/O but bottlenecks can still be triggered by CPU-intensive operations.\n\n```ruby\nLocalBus.subscribe \"cpu.intensive\" do |message|\n  # CPU bound operation can trigger a bottleneck\nend\n```\n\n### Shutdown \u0026 Cleanup\n\nThe Station delays process exit in an attempt to flush the queue and avoid dropped messages.\nThis delay can be configured via the `:wait` option in the constructor (default: 5).\n\n\u003e [!IMPORTANT]\n\u003e This wait time allows for processing pending messages at exit, but is not guaranteed.\n\u003e Factor for potential message loss when designing your system.\n\u003e For example, idempotency _i.e. messages that can be re-published without unintended side effects_.\n\n### Limitations\n\n- The Bus is single-threaded - long-running or CPU-bound subscribers can impact latency\n- The Station may drop messages at process exit _(messages are not persisted between process restarts)_\n- No distributed support - limited to single process _(intra-process)_\n- Large message payloads may impact memory usage, especially under high load\n- No built-in retry mechanism for failed subscribers _(subscribers expose an error property, but you'll need to check and handle such errors)_\n\nConsider these limitations when designing your system architecture.\n\n### Demos \u0026 Benchmarks\n\nThe project includes demo scripts that showcase concurrent processing capabilities:\n\n```bash\nbin/demo-bus     # demonstrates Bus performance\nbin/demo-station # demonstrates Station performance\n```\n\nBoth demos simulate I/O-bound operations _(1 second latency per subscriber)_ to show how LocalBus handles concurrent processing.\n\nFor example,\nLocalBus can process 10 messages with 10 I/O-bound subscribers each in **~1 second instead of 100 seconds**,\non a 10-core system.\n\n## See Also\n\n- [Message Bus](https://github.com/discourse/message_bus) - A reliable and robust messaging bus for Ruby and Rack\n- [Wisper](https://github.com/krisleech/wisper) - A micro library providing Ruby objects with Publish-Subscribe capabilities\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopsoft%2Flocal_bus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhopsoft%2Flocal_bus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopsoft%2Flocal_bus/lists"}