Module: EventHub

Defined in:
lib/eventhub/helper.rb,
lib/eventhub/base.rb,
lib/eventhub/logger.rb,
lib/eventhub/message.rb,
lib/eventhub/sleeper.rb,
lib/eventhub/version.rb,
lib/eventhub/constant.rb,
lib/eventhub/consumer.rb,
lib/eventhub/processor2.rb,
lib/eventhub/execution_id.rb,
lib/eventhub/configuration.rb,
lib/eventhub/docs_renderer.rb,
lib/eventhub/actor_watchdog.rb,
lib/eventhub/base_exception.rb,
lib/eventhub/correlation_id.rb,
lib/eventhub/actor_heartbeat.rb,
lib/eventhub/actor_publisher.rb,
lib/eventhub/actor_listener_amqp.rb,
lib/eventhub/actor_listener_http.rb,
lib/eventhub/patches/celluloid_logger.rb

Overview

EventHub patches for upstream gems.

Celluloid 0.18 (the last released version, ~2016) is incompatible with Ruby 3.x frozen-string-literal defaults. Its ‘Internals::Logger.crash` mutates string literals like:

def crash(string, exception)
  if Celluloid.log_actor_crashes
    string << "\n" << format_exception(exception)   # FrozenError under Ruby 3.x
    error string
  end
  @exception_handlers.each { |h| h.call(exception) }
end

The ‘string << …` raises FrozenError BEFORE the registered exception handlers fire. The actor thread then dies silently:

* no exit event is sent to the supervisor (no restart),
* no exit event is sent to linked sub-actors (they stay alive as zombies),
* no error is logged anywhere.

Externally the symptom is: an actor whose method raises (e.g. our ‘ActorListenerAmqp#restart` raising “Listener amqp is restarting…”) appears to be entering the raise but never actually dies, and the listener never gets restarted. We hit this in 1.28.0 testing: SIGHUP looked like it worked (Configuration reloaded, async.restart enqueued, restart entered) but the listener silently became a zombie.

Upstream fix is unlikely - Celluloid is unmaintained. We prepend a corrected ‘crash` that defrosts the input string before mutating it. Behavior is otherwise identical to the original.

Defined Under Namespace

Modules: Configuration, CorrelationId, ExecutionId, Helper, Patches Classes: ActorHeartbeat, ActorListenerAmqp, ActorListenerHttp, ActorPublisher, ActorWatchdog, BaseException, Consumer, DocsRenderer, LoggerProxy, Message, Processor2, Sleeper, Statistics

Constant Summary collapse

VERSION =
"1.28.2".freeze
EH_X_INBOUND =
"event_hub.inbound"
STATUS_INITIAL =

To be set when dispatcher needs to dispatch to first process step.

0
STATUS_SUCCESS =

To be set to indicate successful processed message. Dispatcher will routes message to the next step.

200
STATUS_RETRY =

To be set to trigger retry cycle controlled by the dispatcher

300
STATUS_RETRY_PENDING =

Set and used by the dispatcher only.

301
STATUS_INVALID =

Set before putting the message into a retry queue. Once message has been retried it will sent do the same step with status.code = STATUS_SUCCESS

400
STATUS_DEADLETTER =

Dispatcher will publish message to the invalid queue.

500
STATUS_SCHEDULE =

that message needs to be dead-lettered. Rejected messages could miss the status.code = STATUS_DEADLETTER due to the RabbitMQ deadletter exchange mechanism.

600
STATUS_SCHEDULE_RETRY =

To be set to trigger scheduler based on schedule block, proceses next process step

601
STATUS_SCHEDULE_PENDING =

To be set to trigger scheduler based on schedule block, retry actual process step

602
STATUS_CODE_TRANSLATION =

Set and used by the dispatcher only. Set before putting the scheduled message to the schedule queue.

{
  STATUS_INITIAL => "STATUS_INITIAL",
  STATUS_SUCCESS => "STATUS_SUCCESS",
  STATUS_RETRY => "STATUS_RETRY",
  STATUS_RETRY_PENDING => "STATUS_RETRY_PENDING",
  STATUS_INVALID => "STATUS_INVALID",
  STATUS_DEADLETTER => "STATUS_DEADLETTER",
  STATUS_SCHEDULE => "STATUS_SCHEDULE",
  STATUS_SCHEDULE_RETRY => "STATUS_SCHEDULE_RETRY",
  STATUS_SCHEDULE_PENDING => "STATUS_SCHEDULE_PENDING"
}

Class Method Summary collapse

Class Method Details

.format_celluloid_exception(ex) ⇒ Object

Format a Celluloid actor exception with the dying actor’s class name so post-mortem analysis can identify which actor died.

Important: inside an actor’s crash flow, ‘Celluloid.current_actor` returns the Proxy::Cell, whose `.class` goes through method_missing / the mailbox - which can hang on a dying actor. Read the raw actor object out of the thread-local instead and walk to the subject class via instance variables (no proxy round-trips).



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/eventhub/base.rb', line 42

def self.format_celluloid_exception(ex)
  actor_name = begin
    actor = Thread.current[:celluloid_actor]
    if actor
      behavior = actor.instance_variable_get(:@behavior)
      subject = behavior&.instance_variable_get(:@subject)
      subject&.class&.name
    end
  rescue
    nil
  end
  prefix = actor_name ? "[#{actor_name}] " : ""
  "#{prefix}Exception occured: #{ex.class}: #{ex.message}"
end

.loggerObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/eventhub/logger.rb', line 43

def self.logger
  unless defined?(@logger)
    base_logger = ::EventHub::Components::MultiLogger.new

    if Configuration.console_log_only
      base_logger.add_device(
        EventHub::Components::Logger.logstash_cloud(Configuration.name,
          Configuration.environment)
      )
    else
      base_logger.add_device(Logger.new($stdout))
      base_logger.add_device(
        EventHub::Components::Logger.logstash(Configuration.name,
          Configuration.environment)
      )
    end

    @logger = LoggerProxy.new(base_logger)
  end
  @logger
end