seapig-server 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/seapig-server +643 -263
- data/lib/seapig-server.rb +1 -2
- data/lib/seapig-server/version.rb +3 -0
- metadata +19 -109
- data/README.rdoc +0 -31
- data/bin/seapig-server-intro +0 -757
- data/lib/seapig/version.rb +0 -3
- data/test/dummy/log/development.log +0 -1665
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/-o-ZYYvENmbyGnbDFt6qAW7obI2QzsO9rM9EA7XlUHg.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/1PVg-zJVu7--eXgp92_OEGf9YMjumXrwhnEbNQdV4h8.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/2R9FCEwuVplMI7I5GoTBtw2Er_ap6H5s2otDK-m5XCk.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/3eEw6ayC9zVKEVm7sgtqdiFFsi-0w4VyeWxR-hk72xg.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/5Lly_CA8DZvPhQV2jDQx-Y6P_y3Ygra9t5jfSlGhHDA.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/66l7LMqenzmlIoboeeCMFEZ6J_6zFdXmqNs-gu79P94.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/6vXjezhMg67rV3BDCPOxLeOVbgCHn0aDwBXCA-N7_To.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/7SWrjVR_xhTD9BCS4ifXzZIDn6dp3guSJjF8v5pYDRc.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/98GLSy3KoIvHccw_zbxLg3FpmPEUKmGUr5oL7YZmvOU.cache +0 -127
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9Kel6H2jL5qJlsGHIcYRbxGaV-rMj_teA3CD1eaUVmk.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9T1WawqT-S8RLLgLI4J-o5mcGyy-wwU2mYMBwTuTl4o.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BXgyeZH3I-u535wy4F0jP3wmfV8m8WIWtXqHJKdBC2Q.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BsW5E247kS632hUZ5-NHcjkfprM3AwsB8dksndomUAY.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/CW1ly2CbYhIQ3PoOCrGTUOxS8a03kI-4spp4REHx6mc.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Duqe6Cf2ThStcjIWL29RZnPilo1v6Vi7p86VOEBKqCs.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/ETKdv5blZ86XWjAQcmxAQq2wK6RT9QvGqd7uV7DK1Iw.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/HCc1doOYzZaBpAX7ozNMpo1ErZFli_aaKFQ73oRipH0.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/KS-8mb2c29Zj9YWoyTAojF-sqg-aKMYQr6FkxGuoBWQ.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Lgka3bEq-I8Y6xz-LVIYNVeNIWkmW1NKDnOHOkYcxtE.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/OI6uxGcnsKavdWTtwDAasU3wPx8QXhzBgV0X2n1KjMQ.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/PgVoTat4a0VNolKPJS5RtDX7XcbAGbsqhquIWgWBBWE.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Tq5PhgpHymowY-e-a3airP7Q2OwxNuNC0792hdlAJRc.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VegRto_fFjOSBWQRgNRnnOiV3yByrpUI9b5IEhRvrDI.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VqL7bJxBiq13xYePLO71Spa6RfD5dFCeRRLGYb5jEqw.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/X5QxTUYFP4VOH_7p2Qh34msJtFA6fOnJ0mM7Zip7dRU.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_2P0GKBCbJ61e8RdTBx4rJr8TsUYKFJYd7N0ZhA7o6k.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_mxi_K1gl7n32DYq8bFfbWrIRQrU3QQiuukc63_UBb4.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/a1-5JrPXbtVr0ytoeAR0GzDsG_rlYUHm_sKEC1VHrrM.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/aShHx921rUO4rZzH4135LWkt4TSWfqhpQMN8JsV2OqE.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/b9Ac87wpHRTGZL-mBacNdb343KQ1426WdjSu03OVlz8.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/beCZt_nFEyk5iX6v4bgC3qrdFMgSM0IgrwxaBTFEFA0.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/bkclf1HVUxdntd8cKLvWGX5Pq-E12kwCwakqLo8RoN4.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/csrXH9BNqM8JCRjroMFCt2hluEvIvM0neY_ZQySl58A.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/fsxRcZUqoELh6-D0RWowwDKHYmUkGzh5HMFuwMMWk1g.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/h2XCtwcdt21JcAtjhfoOuIjifaMeMaYwPcFGnmNc2ng.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +0 -3
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j3q-1jQzykD1sMeX9INl7jygCKtC0XJ8JkWkFQkL6qg.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j4VcbkSejSElTiJk3ehlEcJE9HRTG7T14-wVhUDocaA.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/jU_8gMY9eZiN785s1lakKFjnK5ox1EA-Na1fKxuYcjs.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/q3UrpsuPTq0hMrkTWoYoYEZL4u05PdVc4L5NXKuFDgQ.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/sX4-Kn9knBu7plHg7gUT-ryVktGvmZfacNm1cUysjWs.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/umxcUEQeoCOqF0ZwXlNqJEOyT_vhMK8iGO4--jduIDU.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/urzLHu3R3qKeFqygSDL0NVDcyw8BFEA6i9jFeweVZQ4.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vCljKmhe1Sy9j9TDIB9DrQRa8dYlkPg8nyvsZfqfCmw.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vn6FA8BrDkisPSH4VFlc7tgmkzpx1DsOtDp5C3cdaRU.cache +0 -1
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/wtuO4JuGsyO8emVQL7t1bm1fvl6YzGfBHc3tJJ49uvs.cache +0 -2
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/z9KVC85iQuroh2hLYTGZhgZHDlh7183qTlfed3Uhtnw.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/znvRewDLwMSRAUjlQozzMBQ_IGWvw9fVVOh9aeaXxTA.cache +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/zxZcbwt4p6Q1Z7yZfuWYKTXjetmNZI8MgkKazd4PjoA.cache +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3225ad7e10a412888e773d999c3891af4a69d12
|
4
|
+
data.tar.gz: a9dba35e923b127d8604245d2e70071eb76dc18e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfcc0da84581bf7c38e23e5f53ebffba31f3bf8f1fa4ba92d9050643c44f45adca72eeb7b46f3bafd507aabd867de0759b16af4641dd9b3f9e6750df4520ee72
|
7
|
+
data.tar.gz: 4b613077e54af4b556a9456d1df492f0dd80cf06eab0b33a13c9963d819db113e59d23fd7e53e2b1bb861dab1db7688c59d9114acd80ed4b4e23e8176a89e394
|
data/bin/seapig-server
CHANGED
@@ -6,20 +6,32 @@ require 'oj'
|
|
6
6
|
require 'jsondiff'
|
7
7
|
require 'hana'
|
8
8
|
require 'set'
|
9
|
+
require 'slop'
|
9
10
|
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
OPTIONS = Slop.parse { |o|
|
13
|
+
o.string '-l', '--listen', "Address to listen on (default: 127.0.0.1)", default: "127.0.0.1"
|
14
|
+
o.int '-p', '--port', "Port to listen on (default: 3001)"
|
15
|
+
o.bool '-d', '--debug', 'Show debug messages'
|
16
|
+
o.bool '-dm', '--debug-memory', 'Turn on allocation tracing'
|
17
|
+
o.bool '-v', '--verbose', 'Show info messages'
|
18
|
+
o.on '-h', '--help' do puts o; exit end
|
19
|
+
}
|
20
|
+
|
21
|
+
DEBUG = OPTIONS.debug?
|
22
|
+
INFO = (OPTIONS.verbose? or DEBUG)
|
23
|
+
HOST = OPTIONS["listen"].split(":")[0]
|
24
|
+
PORT = (OPTIONS["port"] or (OPTIONS["listen"].split(':')[1] or "3001").to_i)
|
15
25
|
|
16
26
|
OBJECT_CACHE_SIZE = 1
|
17
27
|
|
18
28
|
$stdout.sync = true
|
29
|
+
$stderr.sync = true
|
19
30
|
|
20
31
|
Oj.default_options = { mode: :compat }
|
21
32
|
|
22
33
|
|
34
|
+
|
23
35
|
module WebSocket
|
24
36
|
module Frame
|
25
37
|
class Data < String
|
@@ -52,11 +64,14 @@ class String
|
|
52
64
|
end
|
53
65
|
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
if OPTIONS["debug-memory"]
|
68
|
+
#require 'memory_profiler'
|
69
|
+
#MemoryProfiler.start
|
70
|
+
require 'objspace'
|
71
|
+
ObjectSpace.trace_object_allocations_start
|
72
|
+
$dump_heap = false
|
73
|
+
Signal.trap("USR1") { $dump_heap = true }
|
74
|
+
end
|
60
75
|
|
61
76
|
#
|
62
77
|
# Code is layered, with each layer only communicating with neighbouring layers (e.g. object store never directly talks to em or sockets).
|
@@ -75,68 +90,159 @@ Signal.trap("USR1") {
|
|
75
90
|
#
|
76
91
|
|
77
92
|
|
78
|
-
|
93
|
+
class SeapigObjectStore
|
79
94
|
|
80
|
-
|
95
|
+
attr_reader :objects_by_id, :consumers, :producers, :connections # only for InternalClient
|
96
|
+
attr_reader :internal_client
|
81
97
|
|
82
|
-
@@producers = {} # {pattern_or_id => {client}}; for assessing spawning possibility
|
83
|
-
@@consumers = {} # {pattern_or_id => {client}}; for assessing spawning need, for assessing holding need
|
84
98
|
|
85
|
-
|
86
|
-
|
99
|
+
def initialize
|
100
|
+
@objects_by_id = {} # {id => object}; stores all existing SeapigObjects
|
87
101
|
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
@connections = {} # {connection_handle => connection}; stores all connections
|
103
|
+
@connections_id_seq = 0 # sequence for connection id assignment
|
104
|
+
@clients_id_seq = 0 # sequence for client id assignment
|
91
105
|
|
106
|
+
@producers = {} # {pattern_or_id => {client}}; for assessing spawning possibility
|
107
|
+
@consumers = {} # {pattern_or_id => {client}}; for assessing spawning need, for assessing holding need
|
92
108
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
109
|
+
@dependents = {} # {id_depended_on => {id_depending}}; for assessing spawning need, for assessing holding need, for assessing reproduction need
|
110
|
+
@dependencies = {} # {id_depending => {id_depended_on}}; for updating dependents
|
111
|
+
|
112
|
+
@queue = [] # [object]; objects in need of production
|
113
|
+
@produced = {} # {id_being_produced => {version}}; for assessing enqueuing/dequeuing need
|
114
|
+
|
115
|
+
@internal_client = InternalClient.new(self)
|
116
|
+
@internal_client.connect
|
117
|
+
|
118
|
+
@internal_client.statistics_entity_register(["connections","count"], last: {retain: true, show: true})
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def connection_register(connection_handle, details)
|
124
|
+
raise "Connection double register" if @connections[connection_handle]
|
125
|
+
id = (@connections_id_seq += 1)
|
126
|
+
@connections[connection_handle] = SeapigConnection.new(id, connection_handle, details)
|
127
|
+
@internal_client.statistics_record(["connections","count"], @connections.size)
|
128
|
+
@internal_client.connections_changed
|
129
|
+
id
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def connection_unregister(connection_handle)
|
134
|
+
raise "Unregister of not registered connection" if not connection = @connections[connection_handle]
|
135
|
+
connection.clients.each_value { |client|
|
136
|
+
client_unregister(connection, client)
|
99
137
|
}
|
138
|
+
@connections.delete(connection_handle)
|
139
|
+
@internal_client.statistics_record(["connections","count"], @connections.size)
|
140
|
+
@internal_client.connections_changed
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def client_register(connection_handle, client_handle, on_object_produce, on_object_update, on_object_destroy)
|
145
|
+
raise "Client registration attempted with non-registered connection" if not connection = @connections[connection_handle]
|
146
|
+
raise "Double client-register" if connection.clients.has_key?(client_handle)
|
147
|
+
id = (@clients_id_seq += 1)
|
148
|
+
connection.clients[client_handle] = SeapigClient.new(id, (client_handle == @internal_client), on_object_produce, on_object_update, on_object_destroy)
|
149
|
+
@internal_client.connections_changed
|
150
|
+
id
|
100
151
|
end
|
101
152
|
|
102
153
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
154
|
+
def client_options_set(connection_handle, client_handle, options)
|
155
|
+
client = @connections[connection_handle].clients[client_handle]
|
156
|
+
client.options = options
|
157
|
+
@internal_client.clients_changed(client)
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def client_unregister(connection_handle, client_handle)
|
162
|
+
raise "Client un-registration attempted with non-registered connection" if not connection = @connections[connection_handle]
|
163
|
+
raise "Attempt to unregister client that has not been registered" if not client = connection.clients[client_handle]
|
164
|
+
client.produces.each { |pattern| producer_unregister(connection_handle, client_handle, pattern) }
|
165
|
+
client.consumes.each { |pattern| consumer_unregister(connection_handle, client_handle, pattern) }
|
166
|
+
version_set(connection_handle, client_handle, client.producing[0],nil,nil) if client.producing
|
167
|
+
connection.clients.delete(client_handle)
|
168
|
+
@internal_client.connections_changed
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
def consumer_register(connection_handle, client_handle, pattern_or_id)
|
173
|
+
client = @connections[connection_handle].clients[client_handle]
|
174
|
+
client.consumes << pattern_or_id
|
175
|
+
changed_objects = []
|
176
|
+
@consumers[pattern_or_id] = Set.new if not @consumers[pattern_or_id]
|
177
|
+
@consumers[pattern_or_id].add(client)
|
178
|
+
SeapigObjectStore.matching(pattern_or_id, @producers.merge(@objects_by_id)).each { |matching_id|
|
179
|
+
@objects_by_id[matching_id].consumer_register(pattern_or_id, client) if @objects_by_id[matching_id]
|
180
|
+
changed_objects << spawn(matching_id).id if not @objects_by_id[matching_id]
|
181
|
+
}
|
182
|
+
@internal_client.objects_changed(changed_objects) if changed_objects.size > 0
|
183
|
+
@internal_client.consumers_changed
|
184
|
+
@internal_client.clients_changed(client)
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def producer_register(connection_handle, client_handle, pattern_or_id)
|
189
|
+
client = @connections[connection_handle].clients[client_handle]
|
190
|
+
client.produces << pattern_or_id
|
191
|
+
changed_objects = []
|
192
|
+
@producers[pattern_or_id] = Set.new if not @producers[pattern_or_id]
|
193
|
+
@producers[pattern_or_id].add(client)
|
194
|
+
SeapigObjectStore.matching(pattern_or_id, @consumers.merge(@dependents)).each { |matching_id|
|
195
|
+
@objects_by_id[matching_id].producer_register(pattern_or_id, client) if @objects_by_id[matching_id]
|
196
|
+
changed_objects << spawn(matching_id).id if not @objects_by_id[matching_id]
|
109
197
|
}
|
110
|
-
|
198
|
+
dequeue(client,nil) if not client.producing
|
199
|
+
@internal_client.objects_changed(changed_objects) if changed_objects.size > 0
|
200
|
+
@internal_client.producers_changed if @internal_client
|
201
|
+
@internal_client.clients_changed(client)
|
111
202
|
end
|
112
203
|
|
113
204
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
205
|
+
def consumer_unregister(connection_handle, client_handle, pattern_or_id)
|
206
|
+
client = @connections[connection_handle].clients[client_handle]
|
207
|
+
raise "Consumer unregister without register" if (not @consumers[pattern_or_id]) or (not @consumers[pattern_or_id].include?(client))
|
208
|
+
client.consumes.delete(pattern_or_id)
|
209
|
+
changed_objects = []
|
210
|
+
@consumers[pattern_or_id].delete(client)
|
211
|
+
@consumers.delete(pattern_or_id) if @consumers[pattern_or_id].size == 0
|
212
|
+
SeapigObjectStore.matching(pattern_or_id,@producers.merge(@objects_by_id)).each { |matching_id|
|
213
|
+
@objects_by_id[matching_id].consumer_unregister(pattern_or_id, client) if @objects_by_id[matching_id]
|
214
|
+
changed_objects << despawn(@objects_by_id[matching_id]).id if @objects_by_id[matching_id] and (not @objects_by_id[matching_id].alive?) and (not @dependents[matching_id])
|
121
215
|
}
|
216
|
+
@internal_client.objects_changed(changed_objects) if changed_objects.size > 0
|
217
|
+
@internal_client.consumers_changed
|
218
|
+
@internal_client.clients_changed(client)
|
122
219
|
end
|
123
220
|
|
124
221
|
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
222
|
+
def producer_unregister(connection_handle, client_handle, pattern_or_id)
|
223
|
+
client = @connections[connection_handle].clients[client_handle]
|
224
|
+
raise "Producer unregister without register" if (not @producers[pattern_or_id]) or (not @producers[pattern_or_id].include?(client))
|
225
|
+
changed_objects = []
|
226
|
+
@producers[pattern_or_id].delete(client)
|
227
|
+
@producers.delete(pattern_or_id) if @producers[pattern_or_id].size == 0
|
228
|
+
client.produces.delete(pattern_or_id)
|
229
|
+
SeapigObjectStore.matching(pattern_or_id,@consumers.merge(@dependents)).each { |matching_id|
|
230
|
+
@objects_by_id[matching_id].producer_unregister(pattern_or_id, client) if @objects_by_id[matching_id]
|
231
|
+
changed_objects << despawn(@objects_by_id[matching_id]).id if @objects_by_id[matching_id] and (not @objects_by_id[matching_id].alive?) and (not @dependents[matching_id])
|
132
232
|
}
|
233
|
+
if client.producing and (pattern_or_id.starexp? ? (client.producing[0] =~ pattern_or_id.starexp) : (client.producing[0] == pattern_or_id)) #FIXME: overlaping production patterns are not supported
|
234
|
+
version_set(connection_handle, client_handle, client.producing[0], nil,nil)
|
235
|
+
end
|
236
|
+
@internal_client.objects_changed(changed_objects) if changed_objects.size > 0
|
237
|
+
@internal_client.producers_changed if @internal_client
|
238
|
+
@internal_client.clients_changed(client)
|
133
239
|
end
|
134
240
|
|
135
241
|
|
136
|
-
def
|
242
|
+
def version_get(connection_handle, client_handle, id, version)
|
137
243
|
raise "version_get called on starexp, that doesn't make sense" if id.starexp?
|
138
|
-
return [0,{}] if not
|
139
|
-
|
244
|
+
return [0,{}] if not @objects_by_id.has_key?(id)
|
245
|
+
@objects_by_id[id].version_get(version)
|
140
246
|
end
|
141
247
|
|
142
248
|
|
@@ -145,76 +251,148 @@ module SeapigObjectStore
|
|
145
251
|
# - false => given version has no data (aka. stall)
|
146
252
|
# - true => given version exists (data unknown)
|
147
253
|
# - nil => given version could not be generated (data unknown)
|
148
|
-
def
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
254
|
+
def version_set(connection_handle, client_handle, id, version, data)
|
255
|
+
|
256
|
+
client = @connections[connection_handle].clients[client_handle]
|
257
|
+
changed_objects = []
|
258
|
+
|
259
|
+
if client.producing and id == client.producing[0]
|
260
|
+
raise "requested_version (%s) not in @produced[id] (%s)"%[client.producing[1].inspect,@produced[id].inspect] if not @produced[id].include?(client.producing[1])
|
261
|
+
@produced[id].delete(client.producing[1])
|
262
|
+
@produced.delete(id) if @produced[id].size == 0
|
263
|
+
client.producing = nil
|
264
|
+
was_producing = true
|
265
|
+
else
|
266
|
+
was_producing = false
|
157
267
|
end
|
158
268
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
next if not (dependent = @@objects_by_id[dependent_id])
|
167
|
-
if dependent.version_needed and dependent.version_needed[id] and version.kind_of?(Integer) and dependent.version_needed[id].kind_of?(Integer) and dependent.version_needed[id] < version
|
168
|
-
dependent.version_needed[id] = version
|
169
|
-
enqueue(dependent)
|
170
|
-
end
|
171
|
-
}
|
269
|
+
object = @objects_by_id[id]
|
270
|
+
if (object or (not data.nil?)) and (@objects_by_id.has_key?(id) or @dependents[id] or @consumers.keys.find { |pattern| id =~ pattern.starexp })
|
271
|
+
changed_objects << (object = spawn(id)).id if not object
|
272
|
+
provided_version_highest_known, provided_version_highest_inferred = object.version_set(data, version)
|
273
|
+
if provided_version_highest_inferred
|
274
|
+
puts "Received version is highest inferred" if DEBUG
|
275
|
+
changed_objects << object.id if not changed_objects.include?(object.id)
|
172
276
|
if version.kind_of? Hash
|
173
|
-
object.
|
174
|
-
old_dependencies = (
|
175
|
-
new_dependencies = (
|
277
|
+
object.version_highest_inferred = {} if not object.version_highest_inferred.kind_of?(Hash)
|
278
|
+
old_dependencies = (@dependencies[id] or Set.new)
|
279
|
+
new_dependencies = (@dependencies[id] = Set.new(version.keys))
|
176
280
|
(new_dependencies - old_dependencies).each { |added_dependency|
|
177
|
-
|
178
|
-
|
281
|
+
added_dependency_version_highest_inferred = @objects_by_id[added_dependency] ? @objects_by_id[added_dependency].version_highest_inferred : nil
|
282
|
+
object.version_highest_inferred[added_dependency] = SeapigObject.version_newer(added_dependency_version_highest_inferred, version[added_dependency])
|
283
|
+
changed_objects.concat dependent_add(added_dependency, object.id)
|
179
284
|
}
|
180
285
|
(old_dependencies & new_dependencies).each { |kept_dependency|
|
181
|
-
|
286
|
+
kept_dependency_version_highest_inferred = @objects_by_id[kept_dependency] ? @objects_by_id[kept_dependency].version_highest_inferred : nil
|
287
|
+
object.version_highest_inferred[kept_dependency] = SeapigObject.version_newer(kept_dependency_version_highest_inferred, version[kept_dependency])
|
182
288
|
}
|
183
289
|
(old_dependencies - new_dependencies).each { |removed_dependency|
|
184
|
-
object.
|
185
|
-
dependent_remove(removed_dependency, object.id)
|
290
|
+
object.version_highest_inferred.delete(removed_dependency)
|
291
|
+
changed_objects.concat dependent_remove(removed_dependency, object.id)
|
186
292
|
}
|
187
293
|
else
|
188
|
-
object.
|
294
|
+
object.version_highest_inferred = version
|
189
295
|
end
|
190
296
|
end
|
297
|
+
if provided_version_highest_known
|
298
|
+
puts "Received version is highest known" if DEBUG
|
299
|
+
changed_objects << object.id if not changed_objects.include?(object.id)
|
300
|
+
(@dependents[id] or Set.new).each { |dependent_id|
|
301
|
+
raise if not @objects_by_id.has_key?(dependent_id)
|
302
|
+
next if not (dependent = @objects_by_id[dependent_id])
|
303
|
+
dependent.version_highest_inferred[id] = version if SeapigObject.version_newer?(dependent.version_highest_inferred[id],version)
|
304
|
+
changed_objects << enqueue(dependent)
|
305
|
+
}
|
306
|
+
end
|
191
307
|
enqueue(object)
|
192
308
|
end
|
193
309
|
|
194
|
-
|
310
|
+
if was_producing and not client.producing
|
311
|
+
dequeue(client,nil)
|
312
|
+
@internal_client.clients_changed(client)
|
313
|
+
end
|
314
|
+
|
315
|
+
changed_objects.compact!
|
316
|
+
@internal_client.objects_changed(changed_objects) if changed_objects.size > 0
|
195
317
|
end
|
196
318
|
|
197
319
|
|
198
|
-
|
199
|
-
|
200
|
-
|
320
|
+
#TODO: add locking
|
321
|
+
def cache_get(connection_handle, client_handle, object_id, key)
|
322
|
+
return nil if not @objects_by_id.has_key?(object_id)
|
323
|
+
@objects_by_id[object_id].cache_get(key)
|
201
324
|
end
|
202
325
|
|
203
326
|
|
204
|
-
def
|
205
|
-
return value if not
|
206
|
-
|
327
|
+
def cache_set(cannection_handle, client_handle, object_id, key, value)
|
328
|
+
return value if not @objects_by_id.has_key?(object_id)
|
329
|
+
@objects_by_id[object_id].cache_set(key, value)
|
207
330
|
value
|
208
331
|
end
|
209
332
|
|
210
333
|
|
334
|
+
def pp
|
335
|
+
[
|
336
|
+
"Objects:", @objects_by_id.values.map { |object| " %s"%[object.inspect] },
|
337
|
+
"Queue:", @queue.map { |object| " %s"%[object.inspect] },
|
338
|
+
"Connections:", @connections.map { |_,connection| " %3s - %s"%[connection.id, connection.clients.map { |_,client| client.pretty_id }.join(", ")] },
|
339
|
+
"Clients:", @connections.map { |_,connection| connection.clients.map { |_,client|
|
340
|
+
" %-22s produces: %s consumes: %s%s"%[client.pretty_id, client.produces.to_a.inspect, client.consumes.to_a.inspect,
|
341
|
+
(client.producing and (" producing: "+client.producing.inspect) or "")]
|
342
|
+
} }.flatten,
|
343
|
+
"Produced:", @produced.map { |object,versions| " %s - %s"%[object,versions.inspect] }
|
344
|
+
].flatten.select { |str| str.size > 0 }.join("\n")+"\n"
|
345
|
+
end
|
346
|
+
|
347
|
+
|
211
348
|
private
|
212
349
|
|
350
|
+
class SeapigConnection
|
351
|
+
|
352
|
+
attr_reader :id, :clients, :connection_handle, :details
|
353
|
+
|
354
|
+
def initialize(id, connection_handle, details)
|
355
|
+
@id = id
|
356
|
+
@connection_handle = connection_handle
|
357
|
+
@details = details
|
358
|
+
@clients = {}
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
class SeapigClient
|
365
|
+
|
366
|
+
attr_reader :consumes, :produces, :internal
|
367
|
+
attr_accessor :options, :producing
|
368
|
+
|
369
|
+
def initialize(id, internal, on_object_produce, on_object_update, on_object_destroy)
|
370
|
+
@id = id
|
371
|
+
@on_object_produce = on_object_produce
|
372
|
+
@on_object_update = on_object_update
|
373
|
+
@on_object_destroy = on_object_destroy
|
374
|
+
@options = {}
|
375
|
+
@consumes = Set.new
|
376
|
+
@produces = Set.new
|
377
|
+
@producing = nil
|
378
|
+
@internal = internal
|
379
|
+
end
|
380
|
+
|
381
|
+
def object_produce(*args); @on_object_produce.call(*args); end
|
382
|
+
def object_update(*args); @on_object_update.call(*args); end
|
383
|
+
def object_destroy(*args); @on_object_destroy.call(*args); end
|
384
|
+
|
385
|
+
def pretty_id
|
386
|
+
(@options["name"] or "")+":"+@id.to_s
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
|
213
391
|
|
214
392
|
class SeapigObject
|
215
393
|
|
216
|
-
attr_reader :id, :versions, :direct_producers, :wildcard_producers
|
217
|
-
attr_accessor :
|
394
|
+
attr_reader :id, :versions, :direct_producers, :direct_consumers, :wildcard_producers, :wildcard_consumers
|
395
|
+
attr_accessor :version_highest_inferred, :state
|
218
396
|
|
219
397
|
def initialize(id)
|
220
398
|
@id = id
|
@@ -223,8 +401,9 @@ private
|
|
223
401
|
@wildcard_consumers = {}
|
224
402
|
@direct_producers = Set.new
|
225
403
|
@wildcard_producers = {}
|
226
|
-
@
|
404
|
+
@version_highest_inferred = nil
|
227
405
|
@cache = []
|
406
|
+
@state = nil
|
228
407
|
end
|
229
408
|
|
230
409
|
|
@@ -232,6 +411,9 @@ private
|
|
232
411
|
@wildcard_consumers.keys.each { |client|
|
233
412
|
client.object_destroy(@id)
|
234
413
|
}
|
414
|
+
(Set.new(@wildcard_producers.keys)+@direct_producers).each { |client|
|
415
|
+
client.object_destroy(@id)
|
416
|
+
}
|
235
417
|
end
|
236
418
|
|
237
419
|
|
@@ -240,40 +422,50 @@ private
|
|
240
422
|
end
|
241
423
|
|
242
424
|
|
243
|
-
def version_set(data,version
|
244
|
-
return false if data == nil
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
discard_below
|
425
|
+
def version_set(data,version)
|
426
|
+
return [false, false] if data == nil
|
427
|
+
provided_version_highest_known = if (data.kind_of?(Hash) or data == false) and SeapigObject.version_newer?(version_highest_known, version)
|
428
|
+
@versions << [SeapigObject.version_clone(version),data]
|
429
|
+
(Set.new(@wildcard_consumers.keys)+@direct_consumers).each { |client| client.object_update(@id, version, data) } if data
|
430
|
+
versions_with_valid_data = 0
|
431
|
+
discard_below = @versions.size - 1
|
432
|
+
while discard_below > 0 and versions_with_valid_data < 1
|
433
|
+
versions_with_valid_data += 1 if @versions[discard_below][1]
|
434
|
+
discard_below -= 1
|
435
|
+
end
|
436
|
+
discard_below.times { @versions.shift }
|
437
|
+
true
|
255
438
|
end
|
256
|
-
|
257
|
-
|
439
|
+
provided_version_highest_inferred = SeapigObject.version_newer?(@version_highest_inferred, version)
|
440
|
+
[provided_version_highest_known, provided_version_highest_inferred]
|
258
441
|
end
|
259
442
|
|
260
443
|
|
261
|
-
def
|
444
|
+
def self.version_clone(version)
|
445
|
+
return nil if version == nil
|
446
|
+
return version if version.kind_of?(Fixnum) or version.kind_of?(Float)
|
447
|
+
version.clone
|
448
|
+
end
|
449
|
+
|
450
|
+
|
451
|
+
def version_highest_known
|
262
452
|
return nil if not @versions[-1]
|
263
453
|
@versions[-1][0]
|
264
454
|
end
|
265
455
|
|
266
456
|
|
267
457
|
def self.version_newer?(latest,vb)
|
268
|
-
|
269
|
-
|
270
|
-
return
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
458
|
+
latest_type = latest.kind_of?(Hash) ? 3 : latest.kind_of?(Array) ? 2 : latest.nil? ? 0 : 1
|
459
|
+
vb_type = vb.kind_of?(Hash) ? 3 : vb.kind_of?(Array) ? 2 : vb.nil? ? 0 : 1
|
460
|
+
return latest_type < vb_type if latest_type != vb_type
|
461
|
+
if latest.kind_of?(Hash)
|
462
|
+
(latest.keys & vb.keys).each { |key|
|
463
|
+
return true if version_newer?(latest[key], vb[key])
|
464
|
+
}
|
465
|
+
return vb.size < latest.size #THINK: is this the right way to go...
|
466
|
+
else
|
467
|
+
return (latest <=> vb) == -1
|
468
|
+
end
|
277
469
|
end
|
278
470
|
|
279
471
|
|
@@ -346,7 +538,7 @@ private
|
|
346
538
|
|
347
539
|
|
348
540
|
def inspect
|
349
|
-
'<SO:%s:%s:%s:%s:%s:%s:%s>'%[@id, @versions
|
541
|
+
'<SO:%s:%s:%s:%s:%s:%s:%s>'%[@id, @versions[-1][0].inspect,@direct_producers.map(&:pretty_id).inspect,@wildcard_producers.keys.map(&:pretty_id).inspect,@direct_consumers.map(&:pretty_id).inspect,@wildcard_consumers.keys.map(&:pretty_id).inspect,@version_highest_inferred.inspect]
|
350
542
|
end
|
351
543
|
|
352
544
|
end
|
@@ -365,96 +557,280 @@ private
|
|
365
557
|
end
|
366
558
|
|
367
559
|
|
368
|
-
def
|
560
|
+
def spawn(id)
|
369
561
|
puts "Creating:\n "+id if DEBUG
|
370
|
-
|
371
|
-
|
372
|
-
|
562
|
+
@objects_by_id[id] = object = SeapigObject.new(id)
|
563
|
+
@producers.each_pair.map { |pattern,clients| clients.each { |client| object.producer_register(pattern,client) if pattern.starexp? and (id =~ pattern.starexp) or (id == pattern) } }
|
564
|
+
@consumers.each_pair.map { |pattern,clients| clients.each { |client| object.consumer_register(pattern,client) if pattern.starexp? and (id =~ pattern.starexp) or (id == pattern) } }
|
373
565
|
enqueue(object)
|
374
566
|
object
|
375
567
|
end
|
376
568
|
|
377
569
|
|
378
|
-
def
|
570
|
+
def despawn(object)
|
379
571
|
puts "Deleting:\n "+object.id if DEBUG
|
380
|
-
raise "Despawning object that should stay alive" if object.alive? or
|
572
|
+
raise "Despawning object that should stay alive" if object.alive? or @dependents[object.id]
|
381
573
|
object.destroy
|
382
|
-
(
|
574
|
+
(@dependencies.delete(object.id) or []).each { |dependency_id|
|
383
575
|
dependent_remove(dependency_id, object.id)
|
384
576
|
}
|
385
|
-
|
577
|
+
@objects_by_id.delete(object.id)
|
386
578
|
end
|
387
579
|
|
388
580
|
|
389
|
-
def
|
390
|
-
|
391
|
-
|
581
|
+
def enqueue(object)
|
582
|
+
puts "Enqueuing: "+object.id if DEBUG
|
583
|
+
if object.version_highest_inferred and object.version_highest_known == object.version_highest_inferred
|
584
|
+
object.state = { current: true } if object.version_highest_known == object.version_highest_inferred
|
585
|
+
@queue.delete(object)
|
586
|
+
puts " No need." if DEBUG
|
587
|
+
nil
|
392
588
|
else
|
393
|
-
return if
|
394
|
-
|
589
|
+
return nil if @queue.include?(object) or (@produced[object.id] and @produced[object.id].include?(object.version_highest_inferred))
|
590
|
+
if object.version_highest_inferred.kind_of?(Hash) and object.version_highest_inferred.find { |dependency, dependency_version|
|
591
|
+
(not dependency_version == 0) and
|
592
|
+
((not @objects_by_id[dependency]) or
|
593
|
+
SeapigObject.version_newer?(@objects_by_id[dependency].version_highest_known, dependency_version))
|
594
|
+
}
|
595
|
+
object.state = { current: false, producing: false, enqueued: false }
|
596
|
+
return object
|
597
|
+
end
|
598
|
+
object.state = { current: false, producing: false, enqueued: true }
|
599
|
+
@queue << object
|
395
600
|
(Set.new(object.direct_producers) + object.wildcard_producers.keys).find { |client|
|
396
|
-
dequeue(client, object) if not
|
601
|
+
dequeue(client, object) if not client.producing
|
397
602
|
}
|
603
|
+
object
|
398
604
|
end
|
399
605
|
end
|
400
606
|
|
401
607
|
|
402
|
-
def
|
403
|
-
object =
|
404
|
-
return false if not
|
405
|
-
version_snapshot =
|
608
|
+
def dequeue(client,object)
|
609
|
+
object = @queue.find { |candidate_object| candidate_object.direct_producers.include?(client) or candidate_object.wildcard_producers.has_key?(client) } if not object
|
610
|
+
return false if not @queue.include?(object)
|
611
|
+
version_snapshot = SeapigObject.version_clone(object.version_highest_inferred)
|
612
|
+
@queue.delete(object)
|
613
|
+
client.producing = [object.id, version_snapshot]
|
614
|
+
(@produced[object.id] ||= Set.new) << version_snapshot
|
615
|
+
puts "Dequeuing: "+object.id+" to: "+client.pretty_id+" expected version: "+object.version_highest_inferred.inspect if DEBUG
|
616
|
+
object.state = { current: false, producing: true }
|
617
|
+
@internal_client.clients_changed(client)
|
406
618
|
client.object_produce(object.id, version_snapshot)
|
407
|
-
|
408
|
-
@@producing[client] = object
|
409
|
-
(@@produced[object.id] ||= Set.new) << version_snapshot
|
619
|
+
object
|
410
620
|
end
|
411
621
|
|
412
622
|
|
413
|
-
def
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
623
|
+
def dependent_add(id, dependent)
|
624
|
+
changed_objects = []
|
625
|
+
@dependents[id] = Set.new if not @dependents[id]
|
626
|
+
@dependents[id] << dependent
|
627
|
+
SeapigObjectStore.matching(id, @producers).each { |matching_id|
|
628
|
+
changed_objects << spawn(matching_id) if not @objects_by_id[matching_id]
|
418
629
|
}
|
630
|
+
changed_objects
|
419
631
|
end
|
420
632
|
|
421
633
|
|
422
|
-
def
|
423
|
-
|
424
|
-
|
425
|
-
|
634
|
+
def dependent_remove(id, dependent)
|
635
|
+
changed_objects = []
|
636
|
+
@dependents[id].delete(dependent)
|
637
|
+
@dependents.delete(id) if @dependents[id].size == 0
|
638
|
+
changed_objects << despawn(@objects_by_id[id]).id if @objects_by_id.include?(id) and (not @objects_by_id[id].alive?) and (not @dependents[id])
|
639
|
+
changed_objects
|
426
640
|
end
|
427
641
|
|
428
642
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
643
|
+
end
|
644
|
+
|
645
|
+
|
646
|
+
|
647
|
+
class InternalClient
|
648
|
+
|
649
|
+
|
650
|
+
def id
|
651
|
+
"InternalClient"
|
436
652
|
end
|
437
653
|
|
438
654
|
|
655
|
+
def initialize(seapig_object_store)
|
656
|
+
@seapig_object_store = seapig_object_store
|
657
|
+
@objects_version = [(ENV["SEAPIG_SERVER_SESSION"] or (Time.new.to_f*1000)).to_i,0]
|
658
|
+
@consumers_version = @objects_version.clone
|
659
|
+
@producers_version = @objects_version.clone
|
660
|
+
@connections_version = @objects_version.clone
|
661
|
+
@statistics_version = @objects_version.clone
|
662
|
+
@statistics_timeslot = nil
|
663
|
+
@statistics = {
|
664
|
+
seconds: { seconds: 1, keep: 60*5, timestamp: nil, entities: {}},
|
665
|
+
minutes: { seconds: 60, keep: 60*5, timestamp: nil, entities: {}},
|
666
|
+
hours: { seconds: 60*60, keep: 24*10, timestamp: nil, entities: {}},
|
667
|
+
days: { seconds: 24*60*60, keep: 365*1, timestamp: nil, entities: {}}
|
668
|
+
}
|
669
|
+
@connected = false
|
670
|
+
end
|
439
671
|
|
440
672
|
|
673
|
+
def connect
|
674
|
+
@seapig_object_store.connection_register(self, {})
|
675
|
+
@seapig_object_store.client_register(self, self, method(:object_produce), Proc.new { raise "object_update called on internal client" }, Proc.new { })
|
676
|
+
@seapig_object_store.client_options_set(self, self, "name"=>"SeapigInternalClient")
|
677
|
+
@seapig_object_store.producer_register(self, self, "SeapigServer::Objects")
|
678
|
+
@seapig_object_store.producer_register(self, self, "SeapigServer::Consumers")
|
679
|
+
@seapig_object_store.producer_register(self, self, "SeapigServer::Producers")
|
680
|
+
@seapig_object_store.producer_register(self, self, "SeapigServer::Connections")
|
681
|
+
@seapig_object_store.producer_register(self, self, "SeapigServer::Statistics")
|
682
|
+
@connected = true
|
683
|
+
end
|
441
684
|
|
442
685
|
|
443
|
-
|
686
|
+
def object_produce(object_id, object_version)
|
687
|
+
case object_id
|
688
|
+
when "SeapigServer::Objects"
|
689
|
+
objects = @seapig_object_store.objects_by_id.values.map { |obj|
|
690
|
+
[ obj.id, {
|
691
|
+
"id"=> obj.id,
|
692
|
+
"state"=> (obj.id == object_id ? { current: true} : Oj.load(Oj.dump(obj.state))),
|
693
|
+
"version_highest_known"=> obj.version_highest_known,
|
694
|
+
"version_highest_inferred"=> Oj.load(Oj.dump(obj.version_highest_inferred)), # do we need to clone here?
|
695
|
+
"consumers"=> (Set.new(obj.wildcard_consumers.keys)+obj.direct_consumers).map { |client| client.pretty_id },
|
696
|
+
"producers"=> (Set.new(obj.wildcard_producers.keys)+obj.direct_producers).map { |client| client.pretty_id }
|
697
|
+
} ]
|
698
|
+
}.to_h
|
699
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Objects", @objects_version.clone, objects)
|
700
|
+
when "SeapigServer::Consumers"
|
701
|
+
consumers = @seapig_object_store.consumers.map { |pattern,clients|
|
702
|
+
[pattern, clients.map { |client| client.pretty_id } ]
|
703
|
+
}.to_h
|
704
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Consumers", @consumers_version.clone, consumers)
|
705
|
+
when "SeapigServer::Producers"
|
706
|
+
producers = @seapig_object_store.producers.map { |pattern,clients|
|
707
|
+
[pattern, clients.map { |client| client.pretty_id } ]
|
708
|
+
}.to_h
|
709
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Producers", @producers_version.clone, producers)
|
710
|
+
when "SeapigServer::Connections"
|
711
|
+
connections = @seapig_object_store.connections.map { |connection_handle, connection|
|
712
|
+
[connection.id, {
|
713
|
+
clients: connection.clients.values.map { |client| [client.pretty_id, {
|
714
|
+
id: client.pretty_id,
|
715
|
+
options: client.options,
|
716
|
+
produces: client.produces.to_a,
|
717
|
+
consumes: client.consumes.to_a,
|
718
|
+
producing: (client.internal ? nil : client.producing)}]}.to_h,
|
719
|
+
details: connection.details
|
720
|
+
} ]
|
721
|
+
}.to_h
|
722
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Connections", @connections_version.clone, connections)
|
723
|
+
when "SeapigServer::Statistics"
|
724
|
+
statistics = @statistics.map { |scale_name, scale|
|
725
|
+
[
|
726
|
+
scale_name,
|
727
|
+
scale.merge(entities: scale[:entities].map { |entity, data|
|
728
|
+
[ entity.join("-"), data.map { |key, value| [key, value.kind_of?(Array) ? value[0...-1] : value] }.to_h ]
|
729
|
+
}.to_h )
|
730
|
+
] }.to_h
|
731
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Statistics", @statistics_version.clone, statistics)
|
732
|
+
end
|
733
|
+
end
|
444
734
|
|
445
735
|
|
736
|
+
def objects_changed(object_ids)
|
737
|
+
return if object_ids.size == 1 and object_ids[0] == "SeapigServer::Objects"
|
738
|
+
@objects_version[1] += 1
|
739
|
+
end
|
740
|
+
|
741
|
+
|
742
|
+
def consumers_changed
|
743
|
+
@consumers_version[1] += 1
|
744
|
+
end
|
745
|
+
|
746
|
+
|
747
|
+
def producers_changed
|
748
|
+
@producers_version[1] += 1
|
749
|
+
end
|
750
|
+
|
751
|
+
|
752
|
+
def clients_changed(client)
|
753
|
+
return if client.internal
|
754
|
+
connections_changed
|
755
|
+
end
|
756
|
+
|
757
|
+
|
758
|
+
def connections_changed
|
759
|
+
return if not @connected
|
760
|
+
@connections_version[1] += 1
|
761
|
+
end
|
762
|
+
|
763
|
+
|
764
|
+
def statistics_entity_register(entity, metrics)
|
765
|
+
@statistics.map { |name,scale|
|
766
|
+
statistics_bump_metrics(scale, @statistics[name][:entities][entity] = { metrics: metrics }.merge(metrics.map { |metric, properites| [metric, []] }.to_h))
|
767
|
+
}
|
768
|
+
end
|
769
|
+
|
770
|
+
def statistics_bump_metrics(scale, data)
|
771
|
+
{ count: 0, sum: 0, average: nil, last: nil, maximum: nil, minimum: nil, histogram: [] }.each { |metric, initial_value|
|
772
|
+
next if not data[metric]
|
773
|
+
data[metric] << initial_value
|
774
|
+
data[metric].shift if data[metric].size > scale[:keep]+1
|
775
|
+
}
|
776
|
+
end
|
777
|
+
|
778
|
+
|
779
|
+
def statistics_timeslot_create
|
780
|
+
@statistics_timeslot = Time.new.to_i
|
781
|
+
@statistics.each { |name, scale|
|
782
|
+
while (next_slot = (scale[:timestamp] or (@statistics_timeslot - scale[:seconds] - 1)) + scale[:seconds]) < @statistics_timeslot
|
783
|
+
scale[:timestamp] = next_slot
|
784
|
+
scale[:entities].each { |entity, data|
|
785
|
+
statistics_bump_metrics(scale, data)
|
786
|
+
data[:metrics].each { |metric, properties|
|
787
|
+
statistics_record(entity, data[metric][-2], name) if properties[:retain] and data[metric][-2]
|
788
|
+
}
|
789
|
+
}
|
790
|
+
end
|
791
|
+
}
|
792
|
+
end
|
793
|
+
|
794
|
+
|
795
|
+
def statistics_record(entity, quantity, only_scale = nil)
|
796
|
+
statistics_timeslot_create if @statistics_timeslot != Time.new.to_i and not only_scale
|
797
|
+
@statistics.each { |name, scale|
|
798
|
+
next if only_scale and name != only_scale
|
799
|
+
next if not data = scale[:entities][entity]
|
800
|
+
data[:last][-1] = quantity if data[:metrics][:last]
|
801
|
+
data[:count][-1] += 1 if data[:metrics][:count]
|
802
|
+
data[:sum][-1] += quantity if data[:metrics][:sum]
|
803
|
+
data[:average][-1] = data[:sum][-1]/data[:count][-1] if data[:metrics][:average]
|
804
|
+
data[:minimum][-1] = quantity if data[:metrics][:minimum] and ((not data[:minimum][-1]) or quantity < data[:minimum][-1])
|
805
|
+
data[:maximum][-1] = quantity if data[:metrics][:maximum] and ((not data[:maximum][-1]) or quantity > data[:maximum][-1])
|
806
|
+
if data[:metrics][:histogram]
|
807
|
+
bucket = (Math.log(quantity,10)*data[:metrics][:histogram][:multiplier]).floor
|
808
|
+
data[:histogram][-1].push 0 while data[:histogram][-1].size < (bucket+1)
|
809
|
+
data[:histogram][-1][bucket] += 1
|
810
|
+
end
|
811
|
+
}
|
812
|
+
end
|
813
|
+
|
814
|
+
|
815
|
+
def bump
|
816
|
+
@statistics_version[1] += 1
|
817
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Statistics", @statistics_version.clone, true)
|
818
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Objects", @objects_version.clone, true)
|
819
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Consumers", @consumers_version.clone, true)
|
820
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Producers", @producers_version.clone, true)
|
821
|
+
@seapig_object_store.version_set(self, self, "SeapigServer::Connections", @connections_version.clone, true)
|
822
|
+
end
|
823
|
+
|
824
|
+
end
|
446
825
|
|
447
|
-
#TODO:
|
448
|
-
# * Refactor to have ClientSpace class/module with Clients inside
|
449
826
|
|
450
827
|
|
451
|
-
class
|
828
|
+
class SeapigWebsocketClient
|
452
829
|
|
453
830
|
attr_reader :produces, :consumes, :socket, :producing, :index, :pong_time
|
454
831
|
attr_accessor :options
|
455
832
|
|
456
833
|
@@clients_by_socket = {}
|
457
|
-
@@count = 0
|
458
834
|
|
459
835
|
|
460
836
|
def self.[](socket)
|
@@ -462,77 +838,68 @@ class Client
|
|
462
838
|
end
|
463
839
|
|
464
840
|
|
465
|
-
def initialize(socket)
|
466
|
-
@
|
467
|
-
puts "Client connected:\n "+@index.to_s if DEBUG
|
841
|
+
def initialize(seapig_object_store, socket)
|
842
|
+
@seapig_object_store = seapig_object_store
|
468
843
|
@socket = socket
|
469
844
|
@options = {}
|
470
|
-
@produces = Set.new
|
471
|
-
@consumes = Set.new
|
472
845
|
@versions = {}
|
473
|
-
@
|
846
|
+
@consumes = Set.new
|
474
847
|
@@clients_by_socket[socket] = self
|
475
848
|
self.pong
|
849
|
+
@seapig_object_store.connection_register(self, {})
|
850
|
+
@index = @seapig_object_store.client_register(self, self, method(:object_produce), method(:object_update), method(:object_destroy))
|
851
|
+
puts "Client connected:\n "+@index.to_s if DEBUG
|
476
852
|
end
|
477
853
|
|
478
854
|
|
479
|
-
def
|
480
|
-
|
855
|
+
def options_set(options)
|
856
|
+
@options = options
|
857
|
+
@seapig_object_store.client_options_set(self, self, options)
|
481
858
|
end
|
482
859
|
|
483
860
|
|
484
|
-
def
|
485
|
-
|
861
|
+
def id #duplicate
|
862
|
+
(@options['name'] or "") + ':' + @index.to_s
|
486
863
|
end
|
487
864
|
|
488
865
|
|
489
866
|
def destroy
|
490
867
|
puts "Client disconnected:\n "+@index.to_s if DEBUG
|
491
868
|
@@clients_by_socket.delete(@socket)
|
492
|
-
@
|
493
|
-
@
|
494
|
-
producing = @producing
|
495
|
-
@producing = nil
|
496
|
-
SeapigObjectStore.version_set(self,producing[0],nil,nil,producing[1]) if producing
|
869
|
+
@seapig_object_store.client_unregister(self, self)
|
870
|
+
@seapig_object_store.connection_unregister(self)
|
497
871
|
end
|
498
872
|
|
499
873
|
|
500
874
|
def producer_register(pattern, known_version)
|
501
|
-
@
|
502
|
-
|
503
|
-
SeapigObjectStore.version_set(self, pattern, known_version, true, false) if known_version and not pattern.starexp?
|
875
|
+
@seapig_object_store.producer_register(self, self, pattern)
|
876
|
+
@seapig_object_store.version_set(self, self, pattern, known_version, true) if known_version and not pattern.starexp?
|
504
877
|
end
|
505
878
|
|
506
879
|
|
507
880
|
def producer_unregister(pattern)
|
508
|
-
@
|
509
|
-
SeapigObjectStore.producer_unregister(pattern, self)
|
510
|
-
if @producing and (pattern.starexp? ? (@producing[0] =~ pattern.starexp) : (@producing[0] == pattern)) #NOTE: overlaping production patterns are not supported
|
511
|
-
producing = @producing
|
512
|
-
@producing = nil
|
513
|
-
SeapigObjectStore.version_set(self,producing[0],nil,nil,producing[1])
|
514
|
-
end
|
881
|
+
@seapig_object_store.producer_unregister(self, self, pattern)
|
515
882
|
end
|
516
883
|
|
517
884
|
|
518
885
|
def consumer_register(pattern, known_version)
|
519
886
|
@consumes.add(pattern)
|
520
887
|
@versions[pattern] = known_version if not pattern.starexp?
|
521
|
-
|
888
|
+
@seapig_object_store.consumer_register(self, self, pattern)
|
522
889
|
gc_versions
|
523
890
|
end
|
524
891
|
|
525
892
|
|
526
893
|
def consumer_unregister(pattern)
|
527
894
|
@consumes.delete(pattern)
|
528
|
-
|
895
|
+
@seapig_object_store.consumer_unregister(self, self, pattern)
|
529
896
|
gc_versions
|
530
897
|
end
|
531
898
|
|
532
899
|
|
533
900
|
def gc_versions
|
534
901
|
@versions.keys.each { |object_id|
|
535
|
-
@versions.delete(object_id) if not
|
902
|
+
@versions.delete(object_id) if not @consumes.find { |pattern|
|
536
903
|
pattern.starexp? and (object_id =~ pattern.starexp) or (pattern == object_id)
|
537
904
|
}
|
538
905
|
}
|
@@ -542,44 +909,40 @@ class Client
|
|
542
909
|
def object_update(object_id, object_version, object_data)
|
543
910
|
#THINK: should we propagate stalls to clients?
|
544
911
|
return if object_version == 0 or object_version == @versions[object_id]
|
545
|
-
old_version, old_data =
|
912
|
+
old_version, old_data = @seapig_object_store.version_get(self, self, object_id, (@versions[object_id] or 0))
|
546
913
|
data = if old_version == 0
|
547
914
|
{ "value" => object_data }
|
548
915
|
else
|
549
|
-
diff =
|
550
|
-
diff =
|
916
|
+
diff = @seapig_object_store.cache_get(self, self, object_id,[:diff,old_version,object_version])
|
917
|
+
diff = @seapig_object_store.cache_set(self, self, object_id,[:diff,old_version,object_version],JsonDiff.generate(old_data, object_data)) if not diff
|
551
918
|
{ "patch" => diff }
|
552
919
|
end
|
553
920
|
|
554
921
|
json = Oj.dump({
|
555
922
|
"action" => 'object-update',
|
556
923
|
"id" => object_id,
|
557
|
-
"
|
558
|
-
"
|
924
|
+
"version-old" => old_version,
|
925
|
+
"version-new" => object_version,
|
559
926
|
}.merge(data))
|
560
927
|
puts "Sending:\n %8iB %s to %s"%[json.size, object_id, id] if DEBUG
|
561
928
|
@versions[object_id] = object_version
|
562
929
|
@socket.send json
|
930
|
+
@seapig_object_store.internal_client.statistics_record(["message","outgoing","count"], 1)
|
931
|
+
@seapig_object_store.internal_client.statistics_record(["message","outgoing","size"], json.size)
|
563
932
|
end
|
564
933
|
|
565
934
|
|
566
935
|
def object_destroy(object_id)
|
936
|
+
@versions.delete(object_id) #TODO: regression test, sub-unsub-sub
|
567
937
|
@socket.send Oj.dump("action" => 'object-destroy', "id" => object_id)
|
568
938
|
end
|
569
939
|
|
570
940
|
|
571
941
|
def object_patch(object_id, patch, value, from_version, to_version)
|
572
942
|
raise "patching wildcard object. no." if object_id.starexp?
|
573
|
-
requested_object_id, requested_version = @producing
|
574
|
-
if requested_object_id == object_id
|
575
|
-
@producing = nil
|
576
|
-
else
|
577
|
-
requested_version = false
|
578
|
-
end
|
579
|
-
new_version = to_version
|
580
943
|
|
581
944
|
new_data = if patch
|
582
|
-
object_version, object_data =
|
945
|
+
object_version, object_data = @seapig_object_store.version_get(self,self,object_id,from_version)
|
583
946
|
print "Patching:\n version: "+object_version.inspect+"\n from_version: "+from_version.inspect+"\n to_version: "+to_version.inspect+"\n patch_size: "+(patch and patch.size.to_s or "nil")+"\n --> " if DEBUG
|
584
947
|
if from_version == object_version
|
585
948
|
puts 'clean' if DEBUG
|
@@ -602,17 +965,15 @@ class Client
|
|
602
965
|
nil
|
603
966
|
end
|
604
967
|
|
605
|
-
|
968
|
+
@seapig_object_store.version_set(self,self,object_id,to_version,new_data)
|
606
969
|
end
|
607
970
|
|
608
971
|
|
609
972
|
def object_produce(object_id, object_version)
|
610
973
|
raise "Can't produce a wildcard object" if object_id.starexp?
|
611
974
|
raise "Client already producing something (producing: %s, trying to assign: %s)"%[@producing.inspect, [object_id,object_version].inspect] if @producing
|
612
|
-
raise "Can't produce that pattern: "+@produces.inspect+" "+object_id.inspect if not @produces.find { |pattern| object_id =~ pattern.starexp }
|
613
975
|
puts "Assigning:\n "+object_id+':'+object_version.inspect+' to: '+self.id if DEBUG
|
614
|
-
@socket.send Oj.dump("action" => 'object-produce', "id" => object_id, "version"=>object_version)
|
615
|
-
@producing = [object_id, object_version]
|
976
|
+
@socket.send Oj.dump("action" => 'object-produce', "id" => object_id, "version-inferred"=>object_version)
|
616
977
|
end
|
617
978
|
|
618
979
|
|
@@ -635,14 +996,9 @@ class Client
|
|
635
996
|
@@clients_by_socket.each_pair { |socket,client| socket.close if Time.new - client.pong_time > 60 }
|
636
997
|
end
|
637
998
|
|
638
|
-
|
639
|
-
def self.pp
|
640
|
-
"Clients:\n"+@@clients_by_socket.values.map { |client| " %-20s produces:%s consumes:%s"%[client.id,client.produces.to_a,client.consumes.to_a] }.join("\n")+"\n"
|
641
|
-
end
|
642
999
|
end
|
643
1000
|
|
644
1001
|
|
645
|
-
|
646
1002
|
#TODO:
|
647
1003
|
# * change protocol to use "pattern" instead of "id"
|
648
1004
|
# * change "object-patch" to something nicer
|
@@ -650,91 +1006,115 @@ end
|
|
650
1006
|
processing_times = []
|
651
1007
|
processing_times_sum = 0
|
652
1008
|
|
1009
|
+
seapig_object_store = SeapigObjectStore.new
|
1010
|
+
|
1011
|
+
seapig_object_store.internal_client.statistics_entity_register(["message","incoming","count"], count: {show: true})
|
1012
|
+
seapig_object_store.internal_client.statistics_entity_register(["message","incoming","size"], count: {show: false}, sum: {show: true}, average: {show: true}, maximum: {show: true}, histogram: {show: true, multiplier: 10})
|
1013
|
+
seapig_object_store.internal_client.statistics_entity_register(["message","outgoing","count"], count: {show: true})
|
1014
|
+
seapig_object_store.internal_client.statistics_entity_register(["message","outgoing","size"], count: {show: false}, sum: {show: true}, average: {show: true}, maximum: {show: true}, histogram: {show: true, multiplier: 10})
|
1015
|
+
seapig_object_store.internal_client.statistics_entity_register(["message","incoming","processing-time"], count: {show: false}, sum: {show: true}, average: {show: true}, maximum: {show: true}, histogram: {show: true, multiplier: 10})
|
1016
|
+
|
1017
|
+
|
1018
|
+
def safety_net
|
1019
|
+
Proc.new { |*args|
|
1020
|
+
begin
|
1021
|
+
yield *args
|
1022
|
+
rescue =>e
|
1023
|
+
puts "*"*70+" EXCEPTION"
|
1024
|
+
p e
|
1025
|
+
e.backtrace.each { |line| puts line }
|
1026
|
+
raise
|
1027
|
+
end
|
1028
|
+
}
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
|
653
1032
|
EM.run {
|
654
1033
|
|
655
1034
|
|
656
1035
|
WebSocket::EventMachine::Server.start(host: HOST, port: PORT) { |client_socket|
|
657
1036
|
|
658
|
-
client_socket.onmessage { |
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
puts Client.pp
|
693
|
-
puts SeapigObjectStore.pp
|
694
|
-
puts "Processing:\n time: %.3fs\n count: %i\n average: %.3fs\n total: %.3fs"%[processing_times[-1], processing_times.size, processing_times_sum / processing_times.size, processing_times_sum]
|
695
|
-
end
|
696
|
-
puts "message:%3i t:%.3fs Σt:%.3fs t̅:%.3fs"%[processing_times.size, processing_times[-1], processing_times_sum, processing_times_sum / processing_times.size,] if INFO and not DEBUG
|
697
|
-
rescue => e
|
698
|
-
puts "Message processing error:\n "
|
699
|
-
p e
|
700
|
-
e.backtrace.each { |line| puts line }
|
701
|
-
raise
|
1037
|
+
client_socket.onmessage &safety_net { |message_json|
|
1038
|
+
started_at = Time.new
|
1039
|
+
client = SeapigWebsocketClient[client_socket]
|
1040
|
+
message = Oj.load message_json
|
1041
|
+
puts "-"*80 + ' ' + Time.new.to_s if DEBUG
|
1042
|
+
print "Message:\n from: %-20s\n action: %-30s\n param: %-50s "%[client.id, message['action'], Oj.dump(message.select { |k,v| ['pattern','id','options'].include?(k) })] if DEBUG
|
1043
|
+
puts if DEBUG
|
1044
|
+
case message['action']
|
1045
|
+
when 'object-producer-register'
|
1046
|
+
next unless message['pattern']
|
1047
|
+
client.producer_register(message['pattern'], message['version-known'])
|
1048
|
+
when 'object-producer-unregister'
|
1049
|
+
next unless message['pattern']
|
1050
|
+
client.producer_unregister(message['pattern'])
|
1051
|
+
when 'object-patch'
|
1052
|
+
next unless message['id']
|
1053
|
+
client.object_patch(message['id'], message['patch'], message['value'], message['version-old'], message['version-new'])
|
1054
|
+
when 'object-consumer-register'
|
1055
|
+
next unless message['pattern']
|
1056
|
+
client.consumer_register(message['pattern'], message['version-known'])
|
1057
|
+
when 'object-consumer-unregister'
|
1058
|
+
next unless message['pattern']
|
1059
|
+
client.consumer_unregister(message['pattern'])
|
1060
|
+
when 'client-options-set'
|
1061
|
+
next unless message['options']
|
1062
|
+
client.options_set(message['options'])
|
1063
|
+
else
|
1064
|
+
raise 'WTF, got message with action: ' + message['action'].inspect
|
1065
|
+
end
|
1066
|
+
processing_times << (Time.new.to_f - started_at.to_f)
|
1067
|
+
processing_times_sum += processing_times[-1]
|
1068
|
+
if DEBUG
|
1069
|
+
puts seapig_object_store.pp
|
1070
|
+
puts "Processing:\n time: %.3fs\n count: %i\n average: %.3fs\n total: %.3fs"%[processing_times[-1], processing_times.size, processing_times_sum / processing_times.size, processing_times_sum]
|
702
1071
|
end
|
1072
|
+
seapig_object_store.internal_client.statistics_record(["message","incoming","count"], 1)
|
1073
|
+
seapig_object_store.internal_client.statistics_record(["message","incoming","size"], message_json.size)
|
1074
|
+
seapig_object_store.internal_client.statistics_record(["message","incoming","processing-time"], (processing_times[-1]*1000000).floor)
|
1075
|
+
puts "message:%3i t:%.3fs Σt:%.3fs t̅:%.3fs"%[processing_times.size, processing_times[-1], processing_times_sum, processing_times_sum / processing_times.size,] if INFO and not DEBUG
|
703
1076
|
}
|
704
1077
|
|
705
1078
|
|
706
|
-
client_socket.onopen {
|
707
|
-
client_socket.onclose {
|
708
|
-
client_socket.onpong {
|
1079
|
+
client_socket.onopen &safety_net { SeapigWebsocketClient.new(seapig_object_store, client_socket) }
|
1080
|
+
client_socket.onclose &safety_net { SeapigWebsocketClient[client_socket].destroy if SeapigWebsocketClient[client_socket] }
|
1081
|
+
client_socket.onpong &safety_net { SeapigWebsocketClient[client_socket].pong }
|
709
1082
|
}
|
710
1083
|
|
711
1084
|
puts "Listening on %s:%s"%[HOST,PORT] if INFO or DEBUG
|
712
|
-
|
1085
|
+
Socket.open(:UNIX, :DGRAM) { |s| s.connect(Socket.pack_sockaddr_un(ENV['NOTIFY_SOCKET'])); s.sendmsg "READY=1" } if ENV['NOTIFY_SOCKET']
|
713
1086
|
|
714
|
-
EM.add_periodic_timer(10
|
715
|
-
EM.add_periodic_timer(10
|
716
|
-
EM.add_periodic_timer(10
|
1087
|
+
EM.add_periodic_timer(10, &safety_net { SeapigWebsocketClient.send_pings })
|
1088
|
+
EM.add_periodic_timer(10, &safety_net { SeapigWebsocketClient.send_heartbeats })
|
1089
|
+
EM.add_periodic_timer(10, &safety_net { SeapigWebsocketClient.check_ping_timeouts })
|
717
1090
|
|
718
|
-
EM.add_periodic_timer(1
|
1091
|
+
EM.add_periodic_timer(1, &safety_net {
|
719
1092
|
now = Time.new
|
720
1093
|
puts "CPU time used: %7.3f%%"%[(processing_times_sum-$last_processing_times_sum)*100.0/(now - $last_cpu_time)] if $last_cpu_time and DEBUG
|
721
1094
|
$last_cpu_time = now
|
722
1095
|
$last_processing_times_sum = processing_times_sum
|
723
|
-
|
1096
|
+
seapig_object_store.internal_client.bump
|
1097
|
+
if $dump_heap
|
1098
|
+
$dump_heap = false
|
1099
|
+
GC.start
|
1100
|
+
#MemoryProfiler.stop.pretty_print(to_file: "memory_"+Time.new.strftime("%Y%m%d%H%M%S"))
|
1101
|
+
#MemoryProfiler.start
|
1102
|
+
open("heap_dump_"+Time.new.strftime("%Y%m%d%H%M%S"),"w") { |file| ObjectSpace.dump_all(output: file) }
|
1103
|
+
end
|
1104
|
+
})
|
724
1105
|
|
725
1106
|
|
726
1107
|
close_reader, close_writer = IO.pipe
|
727
1108
|
|
728
|
-
EM.watch(close_reader
|
1109
|
+
EM.watch(close_reader, &safety_net { |connection|
|
729
1110
|
connection.notify_readable = true
|
730
1111
|
connection.define_singleton_method(:notify_readable) do
|
731
1112
|
puts "Shutting down" if INFO or DEBUG
|
732
1113
|
exit
|
733
1114
|
end
|
734
|
-
}
|
1115
|
+
})
|
735
1116
|
|
736
|
-
Signal.trap("INT") {
|
737
|
-
puts "SIGINT received, scheduling exit." if DEBUG
|
1117
|
+
Signal.trap("INT") { #NOTE: don't puts to std here, it may deadlock
|
738
1118
|
close_writer.write('.')
|
739
1119
|
}
|
740
1120
|
|