Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/instructions/core-karya-rbs.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ RBS in this repository is a correctness contract.
`private`, mirror that visibility change in the same RBS patch.
- Match argument names, keyword names, optionality, return types, and nested
module/class structure.
- Match the full accepted input surface before normalization. If Ruby accepts
aliases, alternative casing, delimiter variants, or `nil` before rejecting or
normalizing, model that accepted input in the RBS instead of only the
normalized canonical value.
- Do not collapse shared contracts to one concrete implementation's keyword
surface. Shared interfaces should model only the common contract they truly
guarantee, not adapter-local boot options copied from the first
implementation.
- Do not use broad keyword maps to dodge exactness. If Ruby requires specific
keys, rejects unknown keys, or distinguishes one optional keyword from
another, encode that explicitly instead of using a generic catch-all keyword
shape.
- Remove stale entries for deleted methods, constants, and modules.
- Do not use `untyped`, `any`, or other generic escape hatches where a concrete
type is knowable.
Expand Down
10 changes: 10 additions & 0 deletions .github/instructions/review.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ For any Ruby change that has a mirrored file under `sig/`:
- **Required:** Treat spec-layout drift as a review concern when extracted
owner-local files leave all direct behavior buried only in a monolithic owner
spec.
- **Required:** Mirror the accepted input surface, not just the normalized
output surface. If Ruby accepts aliases, mixed casing, delimiter variants, or
broader nilability before normalization, the RBS input type must reflect
that same accepted set.
- **Required:** Flag signatures that overfit one implementation while claiming
to model a shared contract. If a shared base/module type is narrowed to one
concrete adapter's keyword set or return posture, treat that as contract
drift even when the current implementation still passes tests.
- **Required:** Flag generic keyword or catch-all type shapes that hide runtime
rules about required keys, rejected keys, or known option names.

## Architecture Guidelines Review

Expand Down
1 change: 1 addition & 0 deletions core/karya/lib/karya.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
require_relative 'karya/outbound_events'
require_relative 'karya/reservation'
require_relative 'karya/queue_store'
require_relative 'karya/backend'
require_relative 'karya/workflow'
require_relative 'karya/constant_resolver'
require_relative 'karya/worker'
Expand Down
22 changes: 22 additions & 0 deletions core/karya/lib/karya/backend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

# Copyright Codevedas Inc. 2025-present
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

module Karya
# Raised when backend selection input cannot be normalized into a supported identifier.
class InvalidBackendSelectionError < Error; end

# Raised when a caller refers to a backend outside the supported backend set.
class UnsupportedBackendError < Error; end

# Namespace for backend selection and lifecycle contracts.
module Backend
autoload :Base, 'karya/backend/base'
autoload :Descriptor, 'karya/backend/descriptor'
autoload :InMemory, 'karya/backend/in_memory'
Comment thread
niteshpurohit marked this conversation as resolved.
autoload :Selection, 'karya/backend/selection'
Comment thread
niteshpurohit marked this conversation as resolved.
Outdated
end
end
35 changes: 35 additions & 0 deletions core/karya/lib/karya/backend/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

# Copyright Codevedas Inc. 2025-present
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

module Karya
module Backend
# Shared backend contract above the queue-store persistence API.
module Base
def identifier
descriptor.identifier
end

def descriptor
raise NotImplementedError, "#{self.class} must implement ##{__method__}"
end

def build_queue_store
raise NotImplementedError, "#{self.class} must implement ##{__method__}"
end

def before_start(queue_store:)
_queue_store = queue_store
nil
end

def after_stop(queue_store:)
Comment thread
niteshpurohit marked this conversation as resolved.
_queue_store = queue_store
nil
end
end
end
end
22 changes: 22 additions & 0 deletions core/karya/lib/karya/backend/descriptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

# Copyright Codevedas Inc. 2025-present
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

require_relative '../primitives/identifier'

Comment thread
niteshpurohit marked this conversation as resolved.
Outdated
module Karya
module Backend
# Immutable backend identity description.
class Descriptor
attr_reader :identifier

def initialize(identifier:)
@identifier = Selection.normalize_identifier(identifier)
Comment thread
niteshpurohit marked this conversation as resolved.
Outdated
freeze
end
end
end
end
66 changes: 66 additions & 0 deletions core/karya/lib/karya/backend/in_memory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

# Copyright Codevedas Inc. 2025-present
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

require_relative '../queue_store/in_memory'

module Karya
module Backend
# Quick-start backend wrapper around the single-process reference queue store.
class InMemory
include Base

DESCRIPTOR = Descriptor.new(identifier: :in_memory)
Comment thread
niteshpurohit marked this conversation as resolved.
Outdated
UNSET = Object.new.freeze
Comment thread
niteshpurohit marked this conversation as resolved.
private_constant :UNSET

def initialize(queue_store_class: QueueStore::InMemory)
@queue_store_class = queue_store_class
end

def descriptor
DESCRIPTOR
end

def build_queue_store(
token_generator: UNSET,
expired_tombstone_limit: UNSET,
completed_batch_retention_limit: UNSET,
max_batch_size: UNSET,
policy_set: UNSET,
circuit_breaker_policy_set: UNSET,
fairness_policy: UNSET
)
queue_store = queue_store_class.new(**{
token_generator:,
expired_tombstone_limit:,
completed_batch_retention_limit:,
max_batch_size:,
policy_set:,
circuit_breaker_policy_set:,
fairness_policy:
}.reject { |_name, value| value.equal?(UNSET) })
return queue_store if queue_store.is_a?(QueueStore::Base)

raise InvalidBackendSelectionError, 'queue_store_class must build a Karya::QueueStore::Base'
end

def before_start(queue_store:)
_queue_store = queue_store
nil
end

def after_stop(queue_store:)
_queue_store = queue_store
nil
end

Comment thread
niteshpurohit marked this conversation as resolved.
Outdated
private

attr_reader :queue_store_class
end
end
end
59 changes: 59 additions & 0 deletions core/karya/lib/karya/backend/selection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

# Copyright Codevedas Inc. 2025-present
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

require_relative '../primitives/identifier'

module Karya
module Backend
# Normalized backend selection without runtime boot wiring.
class Selection
KNOWN_IDENTIFIERS = %w[in_memory sqlite redis postgres mysql].freeze

IDENTIFIER_ALIASES = {
'InMemory' => 'in_memory',
'inmemory' => 'in_memory',
'in_memory' => 'in_memory',
'sqlite' => 'sqlite',
'redis' => 'redis',
'postgres' => 'postgres',
'postgresql' => 'postgres',
'mysql' => 'mysql',
'my_sql' => 'mysql'
}.freeze
Comment thread
niteshpurohit marked this conversation as resolved.
Outdated

attr_reader :identifier

def self.normalize_identifier(value)
normalized_input = normalize_identifier_input(value)
normalized_alias = IDENTIFIER_ALIASES[normalized_input]
return normalized_alias.freeze if normalized_alias

raise UnsupportedBackendError,
"unsupported backend #{normalized_input.inspect}; known backends: #{KNOWN_IDENTIFIERS.join(', ')}"
end

def self.known_identifier?(value)
KNOWN_IDENTIFIERS.include?(normalize_identifier(value))
rescue InvalidBackendSelectionError, UnsupportedBackendError
false
end

def initialize(value)
@identifier = self.class.normalize_identifier(value)
end

def self.normalize_identifier_input(value)
if [NilClass, String, Symbol].any? { |klass| value.is_a?(klass) }
return Primitives::Identifier.new(:backend, value, error_class: InvalidBackendSelectionError).normalize
end

raise InvalidBackendSelectionError, 'backend must be a String or Symbol'
end
private_class_method :normalize_identifier_input
end
end
end
6 changes: 5 additions & 1 deletion core/karya/lib/karya/worker_supervisor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,11 @@ def collect_signal_restorers(restorers, shutdown_controller)

def register_signal_restorers(restorers, shutdown_controller)
SIGNALS.each do |signal|
restorers << runtime.subscribe_signal(signal, -> { shutdown_controller.advance })
restorers << runtime.subscribe_signal(signal, proc do
shutdown_controller.advance
ensure
WakeupSignal.interrupt(WAKEUP_SIGNAL)
end)
Comment thread
niteshpurohit marked this conversation as resolved.
end
end

Expand Down
28 changes: 20 additions & 8 deletions core/karya/lib/karya/worker_supervisor/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class WorkerSupervisor
# Supervisor runtime hooks for process management and signal handling.
class Runtime
OPTION_KEYS = %i[forker instrumenter killer logger outbound_event_dispatcher poll_waiter signal_subscriber waiter].freeze
WAIT_FOR_CHILD_POLL_INTERVAL = 0.05
UNSET = Object.new.freeze
Comment thread
niteshpurohit marked this conversation as resolved.

attr_reader :instrumenter, :logger, :outbound_event_dispatcher, :signal_subscriber
Expand All @@ -29,6 +30,13 @@ def self.default_killer
->(signal, pid) { Process.kill(signal, pid) }
end

def self.default_signal_subscriber
lambda do |signal, handler|
previous_handler = Signal.trap(signal) { handler.call }
-> { Signal.trap(signal, previous_handler) }
end
end

def self.normalize_callable(name, value)
Primitives::Callable.new(name, value, error_class: InvalidWorkerSupervisorConfigurationError).normalize
end
Expand Down Expand Up @@ -66,34 +74,34 @@ def initialize(**attributes)
runtime_class = self.class
@forker = runtime_class.normalize_forker(
:forker,
runtime_class.resolve_option(attributes, :forker, default: method(:default_forker))
resolve_runtime_option(attributes, :forker, default: method(:default_forker))
)
@instrumenter = runtime_class.normalize_optional_callable(
:instrumenter,
runtime_class.resolve_option(attributes, :instrumenter, default: Karya.instrumenter)
resolve_runtime_option(attributes, :instrumenter, default: Karya.instrumenter)
)
@killer = runtime_class.normalize_callable(
:killer,
runtime_class.resolve_option(attributes, :killer, default: runtime_class.default_killer)
resolve_runtime_option(attributes, :killer, default: runtime_class.default_killer)
)
@logger = validate_logger(
runtime_class.resolve_option(attributes, :logger, default: Karya.logger)
resolve_runtime_option(attributes, :logger, default: Karya.logger)
)
@outbound_event_dispatcher = runtime_class.normalize_optional_outbound_event_dispatcher(
:outbound_event_dispatcher,
runtime_class.resolve_option(attributes, :outbound_event_dispatcher, default: Karya.outbound_event_dispatcher)
resolve_runtime_option(attributes, :outbound_event_dispatcher, default: Karya.outbound_event_dispatcher)
)
@poll_waiter = runtime_class.normalize_callable(
:poll_waiter,
runtime_class.resolve_option(attributes, :poll_waiter, default: default_poll_waiter)
resolve_runtime_option(attributes, :poll_waiter, default: default_poll_waiter)
)
@signal_subscriber = runtime_class.normalize_optional_callable(
:signal_subscriber,
runtime_class.resolve_option(attributes, :signal_subscriber, default: nil)
resolve_runtime_option(attributes, :signal_subscriber, default: runtime_class.default_signal_subscriber)
)
@waiter = runtime_class.normalize_callable(
:waiter,
runtime_class.resolve_option(attributes, :waiter, default: default_waiter)
resolve_runtime_option(attributes, :waiter, default: default_waiter)
)
end

Expand Down Expand Up @@ -179,6 +187,10 @@ def default_waiter

private

def resolve_runtime_option(attributes, key, default:)
self.class.resolve_option(attributes, key, default:)
end

def validate_logger(value)
%i[debug info warn error].each do |level|
value.public_method(level)
Expand Down
2 changes: 2 additions & 0 deletions core/karya/sig/karya.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ module Karya
type symbol_options = ::Hash[Symbol, option_value]
type mixed_options = ::Hash[Symbol | String, option_value]
type job_arguments = ::Hash[String, job_argument]
type backend_identifier = "in_memory" | "sqlite" | "redis" | "postgres" | "mysql"
type backend_identifier_input = state_name?
Comment thread
niteshpurohit marked this conversation as resolved.
Outdated
type error_class = singleton(StandardError)
type logger = Internal::_Logger
type instrumenter = ^(String, context_payload) -> nil
Expand Down
15 changes: 15 additions & 0 deletions core/karya/sig/karya/backend.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright Codevedas Inc. 2025-present
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

module Karya
class InvalidBackendSelectionError < Error
end

class UnsupportedBackendError < Error
end

module Backend
end
end
11 changes: 11 additions & 0 deletions core/karya/sig/karya/backend/base.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Karya
module Backend
module Base
def identifier: () -> backend_identifier
def descriptor: () -> Descriptor
def build_queue_store: () -> QueueStore::Base
def before_start: (queue_store: QueueStore::Base) -> nil
def after_stop: (queue_store: QueueStore::Base) -> nil
end
end
end
11 changes: 11 additions & 0 deletions core/karya/sig/karya/backend/descriptor.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Karya
module Backend
class Descriptor
@identifier: backend_identifier

def initialize: (identifier: backend_identifier_input) -> void

attr_reader identifier: backend_identifier
end
end
end
Loading
Loading