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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/seapig-server +643 -263
  3. data/lib/seapig-server.rb +1 -2
  4. data/lib/seapig-server/version.rb +3 -0
  5. metadata +19 -109
  6. data/README.rdoc +0 -31
  7. data/bin/seapig-server-intro +0 -757
  8. data/lib/seapig/version.rb +0 -3
  9. data/test/dummy/log/development.log +0 -1665
  10. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/-o-ZYYvENmbyGnbDFt6qAW7obI2QzsO9rM9EA7XlUHg.cache +0 -0
  11. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/1PVg-zJVu7--eXgp92_OEGf9YMjumXrwhnEbNQdV4h8.cache +0 -1
  12. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/2R9FCEwuVplMI7I5GoTBtw2Er_ap6H5s2otDK-m5XCk.cache +0 -0
  13. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/3eEw6ayC9zVKEVm7sgtqdiFFsi-0w4VyeWxR-hk72xg.cache +0 -0
  14. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/5Lly_CA8DZvPhQV2jDQx-Y6P_y3Ygra9t5jfSlGhHDA.cache +0 -0
  15. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/66l7LMqenzmlIoboeeCMFEZ6J_6zFdXmqNs-gu79P94.cache +0 -1
  16. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/6vXjezhMg67rV3BDCPOxLeOVbgCHn0aDwBXCA-N7_To.cache +0 -2
  17. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/7SWrjVR_xhTD9BCS4ifXzZIDn6dp3guSJjF8v5pYDRc.cache +0 -1
  18. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/98GLSy3KoIvHccw_zbxLg3FpmPEUKmGUr5oL7YZmvOU.cache +0 -127
  19. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9Kel6H2jL5qJlsGHIcYRbxGaV-rMj_teA3CD1eaUVmk.cache +0 -2
  20. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9T1WawqT-S8RLLgLI4J-o5mcGyy-wwU2mYMBwTuTl4o.cache +0 -0
  21. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BXgyeZH3I-u535wy4F0jP3wmfV8m8WIWtXqHJKdBC2Q.cache +0 -0
  22. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BsW5E247kS632hUZ5-NHcjkfprM3AwsB8dksndomUAY.cache +0 -1
  23. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/CW1ly2CbYhIQ3PoOCrGTUOxS8a03kI-4spp4REHx6mc.cache +0 -0
  24. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Duqe6Cf2ThStcjIWL29RZnPilo1v6Vi7p86VOEBKqCs.cache +0 -0
  25. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/ETKdv5blZ86XWjAQcmxAQq2wK6RT9QvGqd7uV7DK1Iw.cache +0 -2
  26. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/HCc1doOYzZaBpAX7ozNMpo1ErZFli_aaKFQ73oRipH0.cache +0 -2
  27. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/KS-8mb2c29Zj9YWoyTAojF-sqg-aKMYQr6FkxGuoBWQ.cache +0 -0
  28. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Lgka3bEq-I8Y6xz-LVIYNVeNIWkmW1NKDnOHOkYcxtE.cache +0 -1
  29. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/OI6uxGcnsKavdWTtwDAasU3wPx8QXhzBgV0X2n1KjMQ.cache +0 -0
  30. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/PgVoTat4a0VNolKPJS5RtDX7XcbAGbsqhquIWgWBBWE.cache +0 -0
  31. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Tq5PhgpHymowY-e-a3airP7Q2OwxNuNC0792hdlAJRc.cache +0 -1
  32. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VegRto_fFjOSBWQRgNRnnOiV3yByrpUI9b5IEhRvrDI.cache +0 -1
  33. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VqL7bJxBiq13xYePLO71Spa6RfD5dFCeRRLGYb5jEqw.cache +0 -0
  34. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/X5QxTUYFP4VOH_7p2Qh34msJtFA6fOnJ0mM7Zip7dRU.cache +0 -1
  35. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_2P0GKBCbJ61e8RdTBx4rJr8TsUYKFJYd7N0ZhA7o6k.cache +0 -0
  36. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_mxi_K1gl7n32DYq8bFfbWrIRQrU3QQiuukc63_UBb4.cache +0 -1
  37. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/a1-5JrPXbtVr0ytoeAR0GzDsG_rlYUHm_sKEC1VHrrM.cache +0 -1
  38. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/aShHx921rUO4rZzH4135LWkt4TSWfqhpQMN8JsV2OqE.cache +0 -0
  39. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/b9Ac87wpHRTGZL-mBacNdb343KQ1426WdjSu03OVlz8.cache +0 -0
  40. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/beCZt_nFEyk5iX6v4bgC3qrdFMgSM0IgrwxaBTFEFA0.cache +0 -1
  41. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/bkclf1HVUxdntd8cKLvWGX5Pq-E12kwCwakqLo8RoN4.cache +0 -1
  42. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/csrXH9BNqM8JCRjroMFCt2hluEvIvM0neY_ZQySl58A.cache +0 -1
  43. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/fsxRcZUqoELh6-D0RWowwDKHYmUkGzh5HMFuwMMWk1g.cache +0 -1
  44. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/h2XCtwcdt21JcAtjhfoOuIjifaMeMaYwPcFGnmNc2ng.cache +0 -1
  45. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +0 -3
  46. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j3q-1jQzykD1sMeX9INl7jygCKtC0XJ8JkWkFQkL6qg.cache +0 -2
  47. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j4VcbkSejSElTiJk3ehlEcJE9HRTG7T14-wVhUDocaA.cache +0 -1
  48. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/jU_8gMY9eZiN785s1lakKFjnK5ox1EA-Na1fKxuYcjs.cache +0 -1
  49. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +0 -2
  50. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/q3UrpsuPTq0hMrkTWoYoYEZL4u05PdVc4L5NXKuFDgQ.cache +0 -0
  51. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/sX4-Kn9knBu7plHg7gUT-ryVktGvmZfacNm1cUysjWs.cache +0 -1
  52. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/umxcUEQeoCOqF0ZwXlNqJEOyT_vhMK8iGO4--jduIDU.cache +0 -1
  53. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/urzLHu3R3qKeFqygSDL0NVDcyw8BFEA6i9jFeweVZQ4.cache +0 -1
  54. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vCljKmhe1Sy9j9TDIB9DrQRa8dYlkPg8nyvsZfqfCmw.cache +0 -0
  55. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vn6FA8BrDkisPSH4VFlc7tgmkzpx1DsOtDp5C3cdaRU.cache +0 -1
  56. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/wtuO4JuGsyO8emVQL7t1bm1fvl6YzGfBHc3tJJ49uvs.cache +0 -2
  57. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/z9KVC85iQuroh2hLYTGZhgZHDlh7183qTlfed3Uhtnw.cache +0 -0
  58. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/znvRewDLwMSRAUjlQozzMBQ_IGWvw9fVVOh9aeaXxTA.cache +0 -0
  59. 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: 02ea92f8574a95efe7cbb84a4273a3f333fa0104
4
- data.tar.gz: d7d0b375e2b6d801387c14420eebc938f15ad2ab
3
+ metadata.gz: b3225ad7e10a412888e773d999c3891af4a69d12
4
+ data.tar.gz: a9dba35e923b127d8604245d2e70071eb76dc18e
5
5
  SHA512:
6
- metadata.gz: ff15f55ebfcce98672f6a820a54c254c1f4d6b5c3055fb4fd50975bec7021291d4fde0535edd1d6b84ee154725c3d315261243a543f9cc91b4c98ce4c118ff72
7
- data.tar.gz: 17650ede8b9698155b93a1699ee61b9f597d805602c77eda65af2c0428d0e7eace0803c02b41e6af4f08292e92f3588fb9883093a1cfafa5a62c49eefe226dc9
6
+ metadata.gz: cfcc0da84581bf7c38e23e5f53ebffba31f3bf8f1fa4ba92d9050643c44f45adca72eeb7b46f3bafd507aabd867de0759b16af4641dd9b3f9e6750df4520ee72
7
+ data.tar.gz: 4b613077e54af4b556a9456d1df492f0dd80cf06eab0b33a13c9963d819db113e59d23fd7e53e2b1bb861dab1db7688c59d9114acd80ed4b4e23e8176a89e394
@@ -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
- DEBUG = (ARGV[0] == "debug")
12
- INFO = (DEBUG or ARGV[0] == "info")
13
- HOST = (ARGV[1] or "127.0.0.1").split(":")[0]
14
- PORT = ((ARGV[1] or '').split(':')[1] or "3001").to_i
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
- Signal.trap("USR1") {
56
- t1 = Time.new; GC.start ;d = Time.new - t1
57
- puts "Long GC run:\n        %.3fs"%(d) if DEBUG and d > 0.05
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
- module SeapigObjectStore
93
+ class SeapigObjectStore
79
94
 
80
- @@objects_by_id = {} # {id => object}; stores all existing SeapigObjects
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
- @@dependents = {} # {id_depended_on => {id_depending}}; for assessing spawning need, for assessing holding need, for assessing reproduction need
86
- @@dependencies = {} # {id_depending => {id_depended_on}}; for updating dependents
99
+ def initialize
100
+ @objects_by_id = {} # {id => object}; stores all existing SeapigObjects
87
101
 
88
- @@queue = [] # [object]; objects in need of production
89
- @@producing = {} # {client => object}; for assessing client busy status
90
- @@produced = {} # {id_being_produced => {version}}; for assessing enqueuing/dequeuing need
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
- def self.consumer_register(pattern_or_id, client)
94
- @@consumers[pattern_or_id] = Set.new if not @@consumers[pattern_or_id]
95
- @@consumers[pattern_or_id].add(client)
96
- self.matching(pattern_or_id, @@producers.merge(@@objects_by_id)).each { |matching_id|
97
- @@objects_by_id[matching_id].consumer_register(pattern_or_id, client) if @@objects_by_id[matching_id]
98
- self.spawn(matching_id) if not @@objects_by_id[matching_id]
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 self.producer_register(pattern_or_id, client)
104
- @@producers[pattern_or_id] = Set.new if not @@producers[pattern_or_id]
105
- @@producers[pattern_or_id].add(client)
106
- self.matching(pattern_or_id, @@consumers.merge(@@dependents)).each { |matching_id|
107
- @@objects_by_id[matching_id].producer_register(pattern_or_id, client) if @@objects_by_id[matching_id]
108
- self.spawn(matching_id) if not @@objects_by_id[matching_id]
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
- self.dequeue(client,nil) if not @@producing[client]
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 self.consumer_unregister(pattern_or_id, client)
115
- raise "Unregister without register" if (not @@consumers[pattern_or_id]) or (not @@consumers[pattern_or_id].include?(client))
116
- @@consumers[pattern_or_id].delete(client)
117
- @@consumers.delete(pattern_or_id) if @@consumers[pattern_or_id].size == 0
118
- self.matching(pattern_or_id,@@producers.merge(@@objects_by_id)).each { |matching_id|
119
- @@objects_by_id[matching_id].consumer_unregister(pattern_or_id, client) if @@objects_by_id[matching_id]
120
- self.despawn(@@objects_by_id[matching_id]) if @@objects_by_id[matching_id] and (not @@objects_by_id[matching_id].alive?) and (not @@dependents[matching_id])
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 self.producer_unregister(pattern_or_id,client)
126
- raise "Unregister without register" if (not @@producers[pattern_or_id]) or (not @@producers[pattern_or_id].include?(client))
127
- @@producers[pattern_or_id].delete(client)
128
- @@producers.delete(pattern_or_id) if @@producers[pattern_or_id].size == 0
129
- self.matching(pattern_or_id,@@consumers.merge(@@dependents)).each { |matching_id|
130
- @@objects_by_id[matching_id].producer_unregister(pattern_or_id, client) if @@objects_by_id[matching_id]
131
- self.despawn(@@objects_by_id[matching_id]) if @@objects_by_id[matching_id] and (not @@objects_by_id[matching_id].alive?) and (not @@dependents[matching_id])
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 self.version_get(client,id,version)
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 @@objects_by_id.has_key?(id)
139
- @@objects_by_id[id].version_get(version)
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 self.version_set(client,id,version,data,requested_version)
149
- raise "Update of pattern doesn't make sense" if id.starexp?
150
-
151
- if requested_version != false
152
- raise "client not in @@producing" if not @@producing[client]
153
- raise "requested_version (%s) not in @@produced[id] (%s)"%[requested_version.inspect,@@produced[id].inspect] if not @@produced[id].include?(requested_version)
154
- @@producing.delete(client)
155
- @@produced[id].delete(requested_version) # also on disconnection / unproducer / test
156
- @@produced.delete(id) if @@produced[id].size == 0
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
- if @@objects_by_id.has_key?(id) or @@dependents[id] or @@consumers.keys.find { |pattern| id =~ pattern.starexp }
160
- object = (@@objects_by_id[id] or self.spawn(id))
161
- accepted = object.version_set(data, version, requested_version)
162
- if accepted
163
- puts "Version accepted" if DEBUG
164
- (@@dependents[id] or Set.new).each { |dependent_id|
165
- raise if not @@objects_by_id.has_key?(dependent_id)
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.version_needed = {} if (not object.version_needed) or object.version_needed.kind_of?(Integer)
174
- old_dependencies = (@@dependencies[id] or Set.new)
175
- new_dependencies = (@@dependencies[id] = Set.new(version.keys))
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
- object.version_needed[added_dependency] = SeapigObject.version_newer((@@objects_by_id[added_dependency] ? @@objects_by_id[added_dependency].version_latest : 0), (version[added_dependency] or 0))
178
- dependent_add(added_dependency, object.id)
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
- object.version_needed[kept_dependency] = SeapigObject.version_newer((@@objects_by_id[kept_dependency] ? @@objects_by_id[kept_dependency].version_latest : 0), (version[kept_dependency] or 0))
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.version_needed.delete(removed_dependency)
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.version_needed = version
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
- dequeue(client,nil) if requested_version != false and not @@producing[client]
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
- def self.cache_get(object_id, key)
199
- return nil if not @@objects_by_id.has_key?(object_id)
200
- @@objects_by_id[object_id].cache_get(key)
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 self.cache_set(object_id, key, value)
205
- return value if not @@objects_by_id.has_key?(object_id)
206
- @@objects_by_id[object_id].cache_set(key, value)
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 :version_needed
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
- @version_needed = nil
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,requested_version)
244
- return false if data == nil
245
- return false if not SeapigObject.version_newer?(version_latest, version)
246
- @version_needed = version if data == true and ((not @version_needed) or SeapigObject.version_newer?(@version_needed, version))
247
- return false if data == true
248
- @versions << [version,data]
249
- (Set.new(@wildcard_consumers.keys)+@direct_consumers).each { |client| client.object_update(@id, version, data) } if data
250
- versions_with_valid_data = 0
251
- discard_below = @versions.size - 1
252
- while discard_below > 0 and versions_with_valid_data < 1
253
- versions_with_valid_data += 1 if @versions[discard_below][1]
254
- discard_below -= 1
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
- discard_below.times { @versions.shift }
257
- true
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 version_latest
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
- # return true if latest.nil? and (not vb.nil?)
269
- # return false if (not latest.nil?) and vb.nil?
270
- return latest < vb if (not latest.kind_of?(Hash)) and (not vb.kind_of?(Hash))
271
- return true if (not latest.kind_of?(Hash)) and ( vb.kind_of?(Hash))
272
- return false if ( latest.kind_of?(Hash)) and (not vb.kind_of?(Hash))
273
- (latest.keys & vb.keys).each { |key|
274
- return true if version_newer?(latest[key], vb[key])
275
- }
276
- return vb.size < latest.size #THINK: is this the right way to go...
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.map { |v| v[0] }.inspect,@direct_producers.map(&:id).inspect,@wildcard_producers.keys.map(&:id).inspect,@direct_consumers.map(&:id).inspect,@wildcard_consumers.keys.map(&:id).inspect,@version_needed.inspect]
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 self.spawn(id)
560
+ def spawn(id)
369
561
  puts "Creating:\n        "+id if DEBUG
370
- @@objects_by_id[id] = object = SeapigObject.new(id)
371
- @@producers.each_pair.map { |pattern,clients| clients.each { |client| object.producer_register(pattern,client) if pattern.starexp? and (id =~ pattern.starexp) or (id == pattern) } }
372
- @@consumers.each_pair.map { |pattern,clients| clients.each { |client| object.consumer_register(pattern,client) if pattern.starexp? and (id =~ pattern.starexp) or (id == pattern) } }
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 self.despawn(object)
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 @@dependents[object.id]
572
+ raise "Despawning object that should stay alive" if object.alive? or @dependents[object.id]
381
573
  object.destroy
382
- (@@dependencies.delete(object.id) or []).each { |dependency_id|
574
+ (@dependencies.delete(object.id) or []).each { |dependency_id|
383
575
  dependent_remove(dependency_id, object.id)
384
576
  }
385
- @@objects_by_id.delete(object.id)
577
+ @objects_by_id.delete(object.id)
386
578
  end
387
579
 
388
580
 
389
- def self.enqueue(object)
390
- if object.version_needed and object.version_latest == object.version_needed
391
- @@queue.delete(object)
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 @@queue.include?(object) or (@@produced[object.id] and @@produced[object.id].include?(object.version_needed))
394
- @@queue << object
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 @@producing[client]
601
+ dequeue(client, object) if not client.producing
397
602
  }
603
+ object
398
604
  end
399
605
  end
400
606
 
401
607
 
402
- def self.dequeue(client,object)
403
- object = @@queue.find { |candidate_object| candidate_object.direct_producers.include?(client) or candidate_object.wildcard_producers.has_key?(client) } if not object
404
- return false if not @@queue.include?(object)
405
- version_snapshot = (object.version_needed == nil ? nil : (object.version_needed.kind_of?(Fixnum) ? object.version_needed : object.version_needed.clone))
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
- @@queue.delete(object)
408
- @@producing[client] = object
409
- (@@produced[object.id] ||= Set.new) << version_snapshot
619
+ object
410
620
  end
411
621
 
412
622
 
413
- def self.dependent_add(id, dependent)
414
- @@dependents[id] = Set.new if not @@dependents[id]
415
- @@dependents[id] << dependent
416
- self.matching(id, @@producers).each { |matching_id|
417
- self.spawn(matching_id) if not @@objects_by_id[matching_id]
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 self.dependent_remove(id, dependent)
423
- @@dependents[id].delete(dependent)
424
- @@dependents.delete(id) if @@dependents[id].size == 0
425
- self.despawn(@@objects_by_id[id]) if @@objects_by_id.include?(id) and (not @@objects_by_id[id].alive?) and (not @@dependents[id])
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
- def self.pp
430
- [
431
- "Objects:", @@objects_by_id.values.map { |object| "        %s"%[object.inspect] }.join("\n"),
432
- "Queue:", @@queue.map { |object| "        %s"%[object.inspect] }.join("\n"),
433
- "Producing:", @@producing.map { |client,object| "        %s - %s"%[client.id,object.id] }.join("\n"),
434
- "Produced:", @@produced.map { |object,versions| "        %s - %s"%[object,versions.inspect] }.join("\n")
435
- ].select { |str| str.size > 0 }.join("\n")+"\n"
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
- end
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 Client
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
- @index = @@count += 1
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
- @producing = nil
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 id
480
- (@options['name'] or "") + ':' + @index.to_s
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 inspect
485
- id
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
- @produces.each { |pattern| SeapigObjectStore.producer_unregister(pattern,self) }
493
- @consumes.each { |pattern| SeapigObjectStore.consumer_unregister(pattern,self) }
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
- @produces.add(pattern)
502
- SeapigObjectStore.producer_register(pattern, self)
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
- @produces.delete(pattern)
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
- SeapigObjectStore.consumer_register(pattern, self)
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
- SeapigObjectStore.consumer_unregister(pattern, self)
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 (@consumes).find { |pattern|
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 = SeapigObjectStore.version_get(self,object_id,(@versions[object_id] or 0))
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 = SeapigObjectStore.cache_get(object_id,[:diff,old_version,object_version])
550
- diff = SeapigObjectStore.cache_set(object_id,[:diff,old_version,object_version],JsonDiff.generate(old_data, object_data)) if not 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
- "old_version" => old_version,
558
- "new_version" => object_version,
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 = SeapigObjectStore.version_get(self,object_id,from_version)
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
- SeapigObjectStore.version_set(self,object_id,new_version,new_data,requested_version)
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 { |message|
659
- begin
660
- started_at = Time.new
661
- client = Client[client_socket]
662
- message = Oj.load message
663
- puts "-"*80 + ' ' + Time.new.to_s if DEBUG
664
- 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
665
- puts if DEBUG
666
- object_id = message['id'] if message['id']
667
- case message['action']
668
- when 'object-producer-register'
669
- fail unless message['pattern']
670
- client.producer_register(message['pattern'],message['known-version'])
671
- when 'object-producer-unregister'
672
- fail unless message['pattern']
673
- client.producer_unregister(message['pattern'])
674
- when 'object-patch'
675
- fail unless message['id']
676
- client.object_patch(object_id,message['patch'], message['value'], message['old_version'], message['new_version'])
677
- when 'object-consumer-register'
678
- fail unless message['id']
679
- client.consumer_register(object_id,message['known-version'])
680
- when 'object-consumer-unregister'
681
- fail unless message['id']
682
- client.consumer_unregister(object_id)
683
- when 'client-options-set'
684
- fail unless message['options']
685
- client.options = message['options']
686
- else
687
- raise 'WTF, got message with action: ' + message['action'].inspect
688
- end
689
- processing_times << (Time.new.to_f - started_at.to_f)
690
- processing_times_sum += processing_times[-1]
691
- if DEBUG
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 { Client.new(client_socket) }
707
- client_socket.onclose { Client[client_socket].destroy if Client[client_socket] }
708
- client_socket.onpong { Client[client_socket].pong }
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
- Socket.open(:UNIX, :DGRAM) { |s| s.connect(Socket.pack_sockaddr_un(ENV['NOTIFY_SOCKET'])); s.sendmsg "READY=1" } if ENV['NOTIFY_SOCKET']
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) { Client.send_pings }
715
- EM.add_periodic_timer(10) { Client.send_heartbeats }
716
- EM.add_periodic_timer(10) { Client.check_ping_timeouts }
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) { |connection|
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