seapig-server 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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