rage-rb 1.28.2 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/lib/rage/cable/cable.rb +84 -24
- data/lib/rage/cable/channel.rb +15 -9
- data/lib/rage/cable/connection.rb +62 -1
- data/lib/rage/cable/protocols/actioncable_v1_json.rb +36 -15
- data/lib/rage/cable/protocols/base.rb +18 -10
- data/lib/rage/cable/protocols/raw_web_socket_json.rb +22 -10
- data/lib/rage/cable/remote_disconnect.rb +69 -0
- data/lib/rage/cable/router.rb +42 -29
- data/lib/rage/code_loader.rb +1 -1
- data/lib/rage/configuration.rb +20 -19
- data/lib/rage/cookies.rb +11 -1
- data/lib/rage/daemon.rb +6 -5
- data/lib/rage/deferred/backends/disk.rb +215 -91
- data/lib/rage/deferred/backends/nil.rb +2 -2
- data/lib/rage/deferred/dead_task.rb +96 -0
- data/lib/rage/deferred/dead_tasks.rb +44 -0
- data/lib/rage/deferred/deferred.rb +28 -1
- data/lib/rage/extension.rb +8 -0
- data/lib/rage/fiber.rb +1 -1
- data/lib/rage/fiber_scheduler.rb +53 -39
- data/lib/rage/internal.rb +4 -0
- data/lib/rage/middleware/origin_validator.rb +35 -12
- data/lib/rage/pubsub/pubsub.rb +146 -1
- data/lib/rage/router/dsl.rb +4 -1
- data/lib/rage/rspec.rb +1 -6
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +6 -1
- metadata +6 -3
|
@@ -44,7 +44,7 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
44
44
|
DEFAULT_PARAMS = {}.freeze
|
|
45
45
|
private_constant :DEFAULT_PARAMS
|
|
46
46
|
|
|
47
|
-
def self.init
|
|
47
|
+
def self.init
|
|
48
48
|
super
|
|
49
49
|
|
|
50
50
|
@ping_connections = Set.new
|
|
@@ -61,8 +61,10 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# @param connection [Rage::Cable::WebSocketConnection] the connection object
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
# @param env [Hash] the Rack environment
|
|
65
|
+
# @param router [Rage::Cable::Router] the Cable router
|
|
66
|
+
def self.on_open(connection, env, router)
|
|
67
|
+
accepted = router.process_connection(connection, env)
|
|
66
68
|
|
|
67
69
|
unless accepted
|
|
68
70
|
connection.write(MESSAGES::UNAUTHORIZED)
|
|
@@ -70,7 +72,7 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
70
72
|
return
|
|
71
73
|
end
|
|
72
74
|
|
|
73
|
-
channel_id =
|
|
75
|
+
channel_id = env["PATH_INFO"].split("/")[-1]
|
|
74
76
|
|
|
75
77
|
channel_name = if channel_id.end_with?("Channel")
|
|
76
78
|
channel_id
|
|
@@ -86,10 +88,10 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
86
88
|
"#{channel_id}Channel"
|
|
87
89
|
end
|
|
88
90
|
|
|
89
|
-
query_string =
|
|
91
|
+
query_string = env["QUERY_STRING"]
|
|
90
92
|
params = query_string == "" ? DEFAULT_PARAMS : Iodine::Rack::Utils.parse_nested_query(query_string)
|
|
91
93
|
|
|
92
|
-
status =
|
|
94
|
+
status = router.process_subscription(connection, env, IDENTIFIER, channel_name, params)
|
|
93
95
|
|
|
94
96
|
if status == :rejected
|
|
95
97
|
connection.write(MESSAGES::REJECTED)
|
|
@@ -101,8 +103,10 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
# @param connection [Rage::Cable::WebSocketConnection] the connection object
|
|
106
|
+
# @param env [Hash] the Rack environment
|
|
104
107
|
# @param raw_data [String] the message body
|
|
105
|
-
|
|
108
|
+
# @param router [Rage::Cable::Router] the Cable router
|
|
109
|
+
def self.on_message(connection, env, raw_data, router)
|
|
106
110
|
if raw_data == "ping"
|
|
107
111
|
@ping_connections << connection
|
|
108
112
|
return
|
|
@@ -110,15 +114,17 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
110
114
|
|
|
111
115
|
data = JSON.parse(raw_data)
|
|
112
116
|
|
|
113
|
-
message_status =
|
|
117
|
+
message_status = router.process_message(connection, env, IDENTIFIER, :receive, data)
|
|
114
118
|
unless message_status == :processed
|
|
115
119
|
connection.write(MESSAGES::UNKNOWN)
|
|
116
120
|
end
|
|
117
121
|
end
|
|
118
122
|
|
|
119
123
|
# @param connection [Rage::Cable::WebSocketConnection] the connection object
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
# @param env [Hash] the Rack environment
|
|
125
|
+
# @param router [Rage::Cable::Router] the Cable router
|
|
126
|
+
def self.on_close(connection, env, router)
|
|
127
|
+
router.process_disconnection(connection, env)
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
# @param data [Object] the object to serialize
|
|
@@ -131,6 +137,12 @@ class Rage::Cable::Protocols::RawWebSocketJson < Rage::Cable::Protocols::Base
|
|
|
131
137
|
false
|
|
132
138
|
end
|
|
133
139
|
|
|
140
|
+
# A unique identifier of the protocol used in multi-server setups by {Rage::PubSub}.
|
|
141
|
+
# @return [String]
|
|
142
|
+
def self.protocol_id
|
|
143
|
+
"rawjson"
|
|
144
|
+
end
|
|
145
|
+
|
|
134
146
|
# @private
|
|
135
147
|
# The base implementation groups connection subscriptions by `params`;
|
|
136
148
|
# however, with `RawWebSocketJson`, params are not part of the payload (see {serialize})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Include this module in your connection class to enable server-initiated disconnections.
|
|
4
|
+
#
|
|
5
|
+
# This allows you to disconnect WebSocket connections from anywhere in your application
|
|
6
|
+
# using {Rage::Cable::Connection.disconnect Rage::Cable::Connection.disconnect}. A common
|
|
7
|
+
# use case is forcing a logout when a user's session is invalidated or their account is suspended.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# class RageCable::Connection < Rage::Cable::Connection
|
|
11
|
+
# include Rage::Cable::RemoteDisconnect
|
|
12
|
+
#
|
|
13
|
+
# identified_by :current_user
|
|
14
|
+
#
|
|
15
|
+
# def connect
|
|
16
|
+
# self.current_user = find_verified_user
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # Later, elsewhere in your application:
|
|
21
|
+
# RageCable::Connection.disconnect(current_user: user)
|
|
22
|
+
#
|
|
23
|
+
module Rage::Cable::RemoteDisconnect
|
|
24
|
+
def self.included(klass)
|
|
25
|
+
klass.extend(ClassMethods)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ClassMethods
|
|
29
|
+
# Disconnect all connections matching the specified identifier.
|
|
30
|
+
# @example Disconnect all connections for a user
|
|
31
|
+
# RageCable::Connection.disconnect(current_user: User.find(1))
|
|
32
|
+
def disconnect(protocol_options: nil, **identified_by)
|
|
33
|
+
raise ArgumentError, "An identifier must be provided" if identified_by.empty?
|
|
34
|
+
raise ArgumentError, "Only one identifier can be provided" if identified_by.size > 1
|
|
35
|
+
|
|
36
|
+
id_pair = identified_by.first
|
|
37
|
+
unless __identifiers&.include?(id_pair[0])
|
|
38
|
+
raise ArgumentError, "Unknown identifier: #{id_pair[0]}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
message = if protocol_options
|
|
42
|
+
raise ArgumentError, "protocol_options must be a Hash" unless protocol_options.is_a?(Hash)
|
|
43
|
+
protocol_options.to_json
|
|
44
|
+
else
|
|
45
|
+
""
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Rage::PubSub.publish(message, topic: ["rage", name, id_pair])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def __remote_disconnect_setup(connection, protocol)
|
|
55
|
+
__identified_by_map.each do |identified_by|
|
|
56
|
+
Rage::PubSub.subscribe(topic: ["rage", self.class.name, identified_by], subscription_id: connection) do |message|
|
|
57
|
+
protocol_options = JSON.parse(message, symbolize_names: true) unless message.empty?
|
|
58
|
+
protocol.on_remote_disconnect(connection, protocol_options)
|
|
59
|
+
connection.close
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def __remote_disconnect_cleanup(connection)
|
|
65
|
+
__identified_by_map&.each do |identified_by|
|
|
66
|
+
Rage::PubSub.unsubscribe(topic: ["rage", self.class.name, identified_by], subscription_id: connection)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/rage/cable/router.rb
CHANGED
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
class Rage::Cable::Router
|
|
4
4
|
# @private
|
|
5
|
-
|
|
5
|
+
# @param protocol [Rage::Cable::Protocols::Base]
|
|
6
|
+
# @param namespace [Module]
|
|
7
|
+
def initialize(protocol, namespace)
|
|
6
8
|
# Hash<String(channel name) => Proc(new channel instance)>
|
|
7
9
|
@channels_map = {}
|
|
10
|
+
@protocol = protocol
|
|
11
|
+
@namespace = namespace
|
|
8
12
|
init_connection_class
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
# Calls the `connect` method on the `Connection` class to handle authentication.
|
|
12
16
|
#
|
|
13
17
|
# @param connection [Rage::Cable::WebSocketConnection] the connection object
|
|
18
|
+
# @param env [Hash] the Rack environment
|
|
14
19
|
# @return [true] if the connection was accepted
|
|
15
20
|
# @return [false] if the connection was rejected
|
|
16
|
-
def process_connection(connection)
|
|
17
|
-
env = connection.env
|
|
18
|
-
|
|
21
|
+
def process_connection(connection, env)
|
|
19
22
|
cable_connection = @connection_class.new(env)
|
|
20
|
-
|
|
21
|
-
cable_connection.connect
|
|
22
|
-
end
|
|
23
|
+
cable_connection.__connect(connection, @protocol)
|
|
23
24
|
|
|
24
25
|
if cable_connection.rejected?
|
|
25
26
|
Rage.logger.debug { "An unauthorized connection attempt was rejected" }
|
|
@@ -34,6 +35,7 @@ class Rage::Cable::Router
|
|
|
34
35
|
# Calls the `subscribed` method on the specified channel.
|
|
35
36
|
#
|
|
36
37
|
# @param connection [Rage::Cable::WebSocketConnection] the connection object
|
|
38
|
+
# @param env [Hash] the Rack environment
|
|
37
39
|
# @param identifier [String] the identifier of the subscription
|
|
38
40
|
# @param channel_name [String] the name of the channel class
|
|
39
41
|
# @param params [Hash] the params hash associated with the subscription
|
|
@@ -41,10 +43,10 @@ class Rage::Cable::Router
|
|
|
41
43
|
# @return [:invalid] if the subscription class does not exist
|
|
42
44
|
# @return [:rejected] if the subscription was rejected
|
|
43
45
|
# @return [:subscribed] if the subscription was accepted
|
|
44
|
-
def process_subscription(connection, identifier, channel_name, params)
|
|
46
|
+
def process_subscription(connection, env, identifier, channel_name, params)
|
|
45
47
|
channel_class = @channels_map[channel_name] || begin
|
|
46
48
|
begin
|
|
47
|
-
klass =
|
|
49
|
+
klass = @namespace.const_get(channel_name, false) unless channel_name.start_with?("::")
|
|
48
50
|
rescue NameError
|
|
49
51
|
nil
|
|
50
52
|
end
|
|
@@ -54,18 +56,18 @@ class Rage::Cable::Router
|
|
|
54
56
|
return :invalid
|
|
55
57
|
end
|
|
56
58
|
|
|
57
|
-
klass.__register_actions.tap do |available_actions|
|
|
58
|
-
Rage.logger.debug { "Compiled #{
|
|
59
|
+
klass.__register_actions(@protocol).tap do |available_actions|
|
|
60
|
+
Rage.logger.debug { "Compiled #{klass.name}. Available remote actions: #{available_actions}." }
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
@channels_map[channel_name] = klass
|
|
62
64
|
end
|
|
63
65
|
|
|
64
|
-
channel = channel_class.new(connection, params,
|
|
66
|
+
channel = channel_class.new(connection, params, env["rage.identified_by"])
|
|
65
67
|
channel.__run_action(:subscribed)
|
|
66
68
|
|
|
67
69
|
if channel.subscription_rejected?
|
|
68
|
-
Rage.logger.debug { "#{
|
|
70
|
+
Rage.logger.debug { "#{channel_class} is transmitting the subscription rejection" }
|
|
69
71
|
# if the subscription is rejected in the `subscribed` method, ActionCable will additionally run
|
|
70
72
|
# the `unsubscribed` method; this makes little sense to me as the client was never subscribed in
|
|
71
73
|
# the first place; additionally, I don't think this behaviour is documented anywhere;
|
|
@@ -73,15 +75,15 @@ class Rage::Cable::Router
|
|
|
73
75
|
# channel.__run_action(:unsubscribed)
|
|
74
76
|
:rejected
|
|
75
77
|
else
|
|
76
|
-
Rage.logger.debug { "#{
|
|
77
|
-
|
|
78
|
+
Rage.logger.debug { "#{channel_class} is transmitting the subscription confirmation" }
|
|
79
|
+
env["rage.cable"][identifier] = channel
|
|
78
80
|
:subscribed
|
|
79
81
|
end
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
# Calls the handler method on the specified channel.
|
|
83
85
|
#
|
|
84
|
-
# @param
|
|
86
|
+
# @param env [Hash] the Rack environment
|
|
85
87
|
# @param identifier [String] the identifier of the subscription
|
|
86
88
|
# @param action_name [Symbol] the name of the handler method
|
|
87
89
|
# @param data [Object] the data sent by the client
|
|
@@ -89,8 +91,8 @@ class Rage::Cable::Router
|
|
|
89
91
|
# @return [:no_subscription] if the client is not subscribed to the specified channel
|
|
90
92
|
# @return [:unknown_action] if the action does not exist on the specified channel
|
|
91
93
|
# @return [:processed] if the message has been successfully processed
|
|
92
|
-
def process_message(
|
|
93
|
-
channel =
|
|
94
|
+
def process_message(_, env, identifier, action_name, data)
|
|
95
|
+
channel = env["rage.cable"][identifier]
|
|
94
96
|
unless channel
|
|
95
97
|
Rage.logger.debug { "Unable to find the subscription" }
|
|
96
98
|
return :no_subscription
|
|
@@ -105,25 +107,36 @@ class Rage::Cable::Router
|
|
|
105
107
|
end
|
|
106
108
|
end
|
|
107
109
|
|
|
110
|
+
# Runs the `unsubscribed` method on a specific channel without closing the connection.
|
|
111
|
+
#
|
|
112
|
+
# @param env [Hash] the Rack environment
|
|
113
|
+
# @param identifier [String] the identifier of the subscription
|
|
114
|
+
def process_unsubscription(_, env, identifier)
|
|
115
|
+
channel = env["rage.cable"].delete(identifier)
|
|
116
|
+
unless channel
|
|
117
|
+
Rage.logger.debug { "Unable to find the subscription" }
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
channel.__run_action(:unsubscribed)
|
|
122
|
+
end
|
|
123
|
+
|
|
108
124
|
# Runs the `unsubscribed` methods on all the channels the client is subscribed to.
|
|
109
125
|
#
|
|
110
126
|
# @param connection [Rage::Cable::WebSocketConnection] the connection object
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
# @param env [Hash] the Rack environment
|
|
128
|
+
def process_disconnection(connection, env)
|
|
114
129
|
env["rage.cable"]&.each do |_, channel|
|
|
115
130
|
channel.__run_action(:unsubscribed)
|
|
116
131
|
end
|
|
117
132
|
|
|
118
133
|
cable_connection = @connection_class.new(env, env["rage.identified_by"])
|
|
119
|
-
|
|
120
|
-
Rage::Telemetry.tracer.span_cable_connection_process(connection: cable_connection, action: :disconnect, env:) do
|
|
121
|
-
cable_connection.disconnect
|
|
122
|
-
end
|
|
134
|
+
cable_connection.__disconnect(connection)
|
|
123
135
|
end
|
|
124
136
|
|
|
125
137
|
# @private
|
|
126
138
|
def reset
|
|
139
|
+
@namespace = Object.const_get(@namespace.name) if @namespace.name
|
|
127
140
|
@channels_map.clear
|
|
128
141
|
init_connection_class
|
|
129
142
|
end
|
|
@@ -131,10 +144,10 @@ class Rage::Cable::Router
|
|
|
131
144
|
private
|
|
132
145
|
|
|
133
146
|
def init_connection_class
|
|
134
|
-
@connection_class = if
|
|
135
|
-
RageCable::Connection
|
|
136
|
-
elsif
|
|
137
|
-
ApplicationCable::Connection
|
|
147
|
+
@connection_class = if @namespace.const_defined?("RageCable::Connection", false)
|
|
148
|
+
@namespace.const_get("RageCable::Connection", false)
|
|
149
|
+
elsif @namespace.const_defined?("ApplicationCable::Connection", false)
|
|
150
|
+
@namespace.const_get("ApplicationCable::Connection", false)
|
|
138
151
|
else
|
|
139
152
|
puts "WARNING: Could not find the RageCable connection class! All connections will be accepted by default."
|
|
140
153
|
Rage::Cable::Connection
|
data/lib/rage/code_loader.rb
CHANGED
|
@@ -85,7 +85,7 @@ class Rage::CodeLoader
|
|
|
85
85
|
|
|
86
86
|
def reload_components
|
|
87
87
|
unless Rage.autoload?(:Cable) # the `Cable` component is loaded
|
|
88
|
-
Rage::Cable.
|
|
88
|
+
ObjectSpace.each_object(Rage::Cable::Router).each(&:reset)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
unless Rage.autoload?(:OpenAPI) # the `OpenAPI` component is loaded
|
data/lib/rage/configuration.rb
CHANGED
|
@@ -757,30 +757,31 @@ class Rage::Configuration
|
|
|
757
757
|
# config.cable.protocol = :raw_websocket_json
|
|
758
758
|
# end
|
|
759
759
|
def protocol=(protocol)
|
|
760
|
-
@protocol =
|
|
761
|
-
when Class
|
|
762
|
-
protocol
|
|
763
|
-
when :actioncable_v1_json
|
|
764
|
-
Rage::Cable::Protocols::ActioncableV1Json
|
|
765
|
-
when :raw_websocket_json
|
|
766
|
-
Rage::Cable::Protocols::RawWebSocketJson
|
|
767
|
-
else
|
|
768
|
-
raise ArgumentError, "Unknown protocol. Supported values are `:actioncable_v1_json` and `:raw_websocket_json`."
|
|
769
|
-
end
|
|
760
|
+
@protocol = Rage::Cable.__protocol_for(protocol)
|
|
770
761
|
end
|
|
771
762
|
|
|
772
763
|
# @private
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
764
|
+
# Returns the list of middleware to be used with Cable applications.
|
|
765
|
+
#
|
|
766
|
+
# When `include_http_middleware` is `true`, the returned list includes both Cable-specific
|
|
767
|
+
# middleware and the application's HTTP middleware (excluding {Rage::FiberWrapper}).
|
|
768
|
+
# When `include_http_middleware` is `false`, only Cable-specific middleware is returned.
|
|
769
|
+
#
|
|
770
|
+
# @param include_http_middleware [Boolean] whether to include the application's HTTP middleware.
|
|
771
|
+
# @return [Array<Array>] an array of middleware definitions, where each definition is an array containing `[middleware_class, args, block]`
|
|
772
|
+
def middlewares(include_http_middleware: true)
|
|
773
|
+
cable_middlewares = if @disable_request_forgery_protection
|
|
774
|
+
[]
|
|
775
|
+
else
|
|
776
|
+
[[Rage::OriginValidator, Array(@allowed_request_origins), nil]]
|
|
777
|
+
end
|
|
780
778
|
|
|
781
|
-
|
|
782
|
-
|
|
779
|
+
if include_http_middleware
|
|
780
|
+
cable_middlewares + Rage.config.middleware.middlewares.reject do |m, _, _|
|
|
781
|
+
m == Rage::FiberWrapper
|
|
783
782
|
end
|
|
783
|
+
else
|
|
784
|
+
cable_middlewares
|
|
784
785
|
end
|
|
785
786
|
end
|
|
786
787
|
end
|
data/lib/rage/cookies.rb
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "base64"
|
|
4
3
|
require "time"
|
|
5
4
|
|
|
5
|
+
begin
|
|
6
|
+
require "base64"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
fail <<~ERR
|
|
9
|
+
|
|
10
|
+
Rage depends on `base64` to encode cookies. Ensure the following line is added to your Gemfile:
|
|
11
|
+
gem "base64"
|
|
12
|
+
|
|
13
|
+
ERR
|
|
14
|
+
end
|
|
15
|
+
|
|
6
16
|
if !defined?(DomainName)
|
|
7
17
|
fail <<~ERR
|
|
8
18
|
|
data/lib/rage/daemon.rb
CHANGED
|
@@ -159,11 +159,7 @@ class Rage::Daemon
|
|
|
159
159
|
private
|
|
160
160
|
|
|
161
161
|
def __execute
|
|
162
|
-
|
|
163
|
-
@__stopping = true
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
Fiber.schedule do
|
|
162
|
+
f = Fiber.schedule do
|
|
167
163
|
backoff = INITIAL_BACKOFF
|
|
168
164
|
|
|
169
165
|
Rage.logger.with_context(daemon: name) do
|
|
@@ -193,6 +189,11 @@ class Rage::Daemon
|
|
|
193
189
|
end
|
|
194
190
|
end
|
|
195
191
|
end
|
|
192
|
+
|
|
193
|
+
Iodine.on_state(:start_shutdown) do
|
|
194
|
+
@__stopping = true
|
|
195
|
+
f.kill
|
|
196
|
+
end
|
|
196
197
|
end # class << self
|
|
197
198
|
|
|
198
199
|
def __cleanup(instance)
|