sumac 0.0.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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +2 -0
  4. data/lib/core_extensions.rb +207 -0
  5. data/lib/sumac.rb +94 -0
  6. data/lib/sumac/adapter.rb +5 -0
  7. data/lib/sumac/adapter/closed.rb +7 -0
  8. data/lib/sumac/argument_error.rb +5 -0
  9. data/lib/sumac/call_dispatcher.rb +70 -0
  10. data/lib/sumac/call_processor.rb +93 -0
  11. data/lib/sumac/closed_error.rb +5 -0
  12. data/lib/sumac/closer.rb +53 -0
  13. data/lib/sumac/connection.rb +94 -0
  14. data/lib/sumac/exposed_object.rb +131 -0
  15. data/lib/sumac/exposed_object_child.rb +158 -0
  16. data/lib/sumac/forgoten.rb +5 -0
  17. data/lib/sumac/handshake.rb +48 -0
  18. data/lib/sumac/id_allocator.rb +99 -0
  19. data/lib/sumac/local_reference.rb +16 -0
  20. data/lib/sumac/local_references.rb +80 -0
  21. data/lib/sumac/message.rb +19 -0
  22. data/lib/sumac/message/exchange.rb +30 -0
  23. data/lib/sumac/message/exchange/base.rb +15 -0
  24. data/lib/sumac/message/exchange/call_request.rb +96 -0
  25. data/lib/sumac/message/exchange/call_response.rb +83 -0
  26. data/lib/sumac/message/exchange/compatibility_notification.rb +53 -0
  27. data/lib/sumac/message/exchange/forget_notification.rb +77 -0
  28. data/lib/sumac/message/exchange/id.rb +30 -0
  29. data/lib/sumac/message/exchange/initialization_notification.rb +52 -0
  30. data/lib/sumac/message/exchange/notification.rb +9 -0
  31. data/lib/sumac/message/exchange/shutdown_notification.rb +27 -0
  32. data/lib/sumac/message/object.rb +72 -0
  33. data/lib/sumac/message/object/array.rb +57 -0
  34. data/lib/sumac/message/object/base.rb +21 -0
  35. data/lib/sumac/message/object/boolean.rb +45 -0
  36. data/lib/sumac/message/object/exception.rb +66 -0
  37. data/lib/sumac/message/object/exposed.rb +79 -0
  38. data/lib/sumac/message/object/exposed_child.rb +86 -0
  39. data/lib/sumac/message/object/float.rb +45 -0
  40. data/lib/sumac/message/object/hash_table.rb +75 -0
  41. data/lib/sumac/message/object/integer.rb +45 -0
  42. data/lib/sumac/message/object/native_exception.rb +56 -0
  43. data/lib/sumac/message/object/null.rb +44 -0
  44. data/lib/sumac/message/object/string.rb +45 -0
  45. data/lib/sumac/message_error.rb +5 -0
  46. data/lib/sumac/messenger.rb +65 -0
  47. data/lib/sumac/native_error.rb +17 -0
  48. data/lib/sumac/no_method_error.rb +9 -0
  49. data/lib/sumac/reference.rb +68 -0
  50. data/lib/sumac/remote_entry.rb +42 -0
  51. data/lib/sumac/remote_object.rb +39 -0
  52. data/lib/sumac/remote_object_child.rb +38 -0
  53. data/lib/sumac/remote_reference.rb +16 -0
  54. data/lib/sumac/remote_references.rb +70 -0
  55. data/lib/sumac/scheduler.rb +56 -0
  56. data/lib/sumac/shutdown.rb +32 -0
  57. data/lib/sumac/stale_object_error.rb +5 -0
  58. data/lib/sumac/unexposable_object_error.rb +9 -0
  59. data/lib/sumac/worker_pool.rb +35 -0
  60. data/test/test_id_allocator.rb +25 -0
  61. metadata +145 -0
@@ -0,0 +1,158 @@
1
+ class Sumac
2
+ module ExposedObjectChild
3
+
4
+ def self.included(base)
5
+ base.extend(IncludedClassMethods)
6
+ base.include(IncludedInstanceMethods)
7
+ end
8
+
9
+ def self.extended(base)
10
+ base.extend(ExtendedClassMethods)
11
+ end
12
+
13
+
14
+ module IncludedClassMethods
15
+
16
+ def inherited(base)
17
+ base.instance_variable_set(:@__exposed_methods__, @__exposed_methods__.dup)
18
+ base.instance_variable_set(:@__parent_accessor__, @__parent_accessor__)
19
+ base.instance_variable_set(:@__key_accessor__, @__key_accessor__)
20
+ end
21
+
22
+ attr_reader :__parent_accessor__, :__key_accessor__
23
+
24
+ def __exposed_methods__
25
+ @__exposed_methods__ ||= []
26
+ end
27
+
28
+ def expose(*method_names)
29
+ raise ArgumentError, 'at least one argument expected' if method_names.empty?
30
+ unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
31
+ raise 'only symbols or strings expected'
32
+ end
33
+ @__exposed_methods__ ||= []
34
+ @__exposed_methods__.concat(method_names.map(&:to_s))
35
+ end
36
+
37
+ def parent_accessor(method_name = nil)
38
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
39
+ raise ArgumentError, "'parent_accessor' expects a method name as a string for symbol"
40
+ end
41
+ @__parent_accessor__ = method_name.to_s
42
+ end
43
+
44
+ def key_accessor(method_name = nil)
45
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
46
+ raise ArgumentError, "'parent_accessor' expects a method name as a string for symbol"
47
+ end
48
+ @__key_accessor__ = method_name.to_s
49
+ end
50
+
51
+ end
52
+
53
+ module IncludedInstanceMethods
54
+
55
+ def __exposed_methods__
56
+ @__exposed_methods__ ||= []
57
+ @__exposed_methods__ + self.class.__exposed_methods__
58
+ end
59
+
60
+ def __parent__
61
+ raise 'parent_accessor not defined' unless __parent_accessor__
62
+ __send__(__parent_accessor__)
63
+ end
64
+
65
+ def __parent_accessor__
66
+ @__parent_accessor__ || self.class.__parent_accessor__
67
+ end
68
+
69
+ def __key__
70
+ raise 'key_accessor not defined' unless __key_accessor__
71
+ __send__(__key_accessor__)
72
+ end
73
+
74
+ def __key_accessor__
75
+ @__key_accessor__ || self.class.__key_accessor__
76
+ end
77
+
78
+ def __sumac_exposed_object__
79
+ end
80
+
81
+ def expose(*method_names)
82
+ raise ArgumentError, 'at least one argument expected' if method_names.empty?
83
+ unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
84
+ raise 'only symbols or strings expected'
85
+ end
86
+ @__exposed_methods__ ||= []
87
+ @__exposed_methods__.concat(method_names.map(&:to_s))
88
+ end
89
+
90
+ def parent_accessor(method_name = nil)
91
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
92
+ raise ArgumentError, "'parent_accessor' expects a method name as a string for symbol"
93
+ end
94
+ @__parent_accessor__ = method_name.to_s
95
+ end
96
+
97
+ def key_accessor(method_name = nil)
98
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
99
+ raise ArgumentError, "'parent_accessor' expects a method name as a string for symbol"
100
+ end
101
+ @__key_accessor__ = method_name.to_s
102
+ end
103
+
104
+ end
105
+
106
+
107
+ module ExtendedClassMethods
108
+
109
+ def inherited(base)
110
+ base.instance_variable_set(:@__exposed_methods__, @__exposed_methods__.dup)
111
+ base.instance_variable_set(:@__parent_accessor__, @__parent_accessor__)
112
+ base.instance_variable_set(:@__key_accessor__, @__key_accessor__)
113
+ end
114
+
115
+ def __exposed_methods__
116
+ @__exposed_methods__ ||= []
117
+ end
118
+
119
+ def __parent__
120
+ raise 'parent_accessor not defined' unless @__parent_accessor__
121
+ __send__(@__parent_accessor__)
122
+ end
123
+
124
+ def __key__
125
+ raise 'key_accessor not defined' unless @__key_accessor__
126
+ __send__(@__key_accessor__)
127
+ end
128
+
129
+ def __sumac_exposed_object__
130
+ end
131
+
132
+ def expose(*method_names)
133
+ raise ArgumentError, 'at least one argument expected' if method_names.empty?
134
+ unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
135
+ raise 'only symbols or strings expected'
136
+ end
137
+ @__exposed_methods__ ||= []
138
+ @__exposed_methods__.concat(method_names.map(&:to_s))
139
+ end
140
+
141
+ def parent_accessor(method_name = nil)
142
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
143
+ raise ArgumentError, "'parent_accessor' expects a method name as a string for symbol"
144
+ end
145
+ @__parent_accessor__ = method_name.to_s
146
+ end
147
+
148
+ def key_accessor(method_name = nil)
149
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
150
+ raise ArgumentError, "'parent_accessor' expects a method name as a string for symbol"
151
+ end
152
+ @__key_accessor__ = method_name.to_s
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+ end
@@ -0,0 +1,5 @@
1
+ class Sumac
2
+ class Forgoten < StandardError
3
+
4
+ end
5
+ end
@@ -0,0 +1,48 @@
1
+ class Sumac
2
+ class Handshake
3
+ include Emittable
4
+
5
+ def initialize(connection)
6
+ raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
7
+ @connection = connection
8
+ end
9
+
10
+ def send_compatibility_notification
11
+ message = Message::Exchange::CompatibilityNotification.new(@connection)
12
+ message.protocol_version = 1
13
+ @connection.messenger.send(message)
14
+ nil
15
+ end
16
+
17
+ def send_initialization_notification
18
+ message = Message::Exchange::InitializationNotification.new(@connection)
19
+ begin
20
+ message.entry = @connection.local_entry
21
+ rescue UnexposableObjectError
22
+ @connection.to(:kill)
23
+ else
24
+ @connection.messenger.send(message)
25
+ end
26
+ nil
27
+ end
28
+
29
+ def receive(message)
30
+ case message
31
+ when Message::Exchange::CompatibilityNotification
32
+ raise MessageError unless @connection.at?(:compatibility_handshake)
33
+ #unless message.protocol_version == 1
34
+ # @connection.to(:kill)
35
+ #end
36
+ @connection.to(:initialization_handshake)
37
+ when Message::Exchange::InitializationNotification
38
+ raise MessageError unless @connection.at?(:initialization_handshake)
39
+ @connection.to(:active)
40
+ @connection.remote_entry.set(message.entry)
41
+ else
42
+ raise MessageError
43
+ end
44
+ nil
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,99 @@
1
+ class Sumac
2
+ class IDAllocator
3
+
4
+ def self.valid?(id)
5
+ id.is_a?(Integer) && id >= 0
6
+ end
7
+
8
+ def initialize
9
+ @allocated_ranges = []
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def valid?(id)
14
+ self.class.valid?(id)
15
+ end
16
+
17
+ def allocate
18
+ @mutex.lock
19
+ if @allocated_ranges.empty?
20
+ id = 0
21
+ elsif @allocated_ranges.first.first == 0
22
+ id = @allocated_ranges.first.last.succ
23
+ else
24
+ id = 0
25
+ end
26
+
27
+ preceding_range = @allocated_ranges.take_while{ |range| range.last < id }.last
28
+ preceding_range_index = @allocated_ranges.find_index(preceding_range) if preceding_range
29
+
30
+ following_range_index = @allocated_ranges.find_index { |range| range.first > id }
31
+ following_range = @allocated_ranges[following_range_index] if following_range_index
32
+
33
+ immediately_preceding_range = preceding_range if preceding_range && preceding_range.last.succ == id
34
+ immediately_following_range = following_range if following_range && following_range.first.pred == id
35
+
36
+ if immediately_preceding_range && immediately_following_range
37
+ @allocated_ranges[preceding_range_index] = (preceding_range.first..following_range.last)
38
+ @allocated_ranges.delete(following_range)
39
+ elsif immediately_preceding_range
40
+ @allocated_ranges[preceding_range_index] = (preceding_range.first..id)
41
+ elsif immediately_following_range
42
+ @allocated_ranges[following_range_index] = (id..following_range.last)
43
+ else
44
+ new_index = preceding_range ? preceding_range_index.succ : 0
45
+ @allocated_ranges.insert(new_index, (id..id))
46
+ end
47
+
48
+ @mutex.unlock
49
+
50
+ if block_given?
51
+ begin
52
+ yield(id)
53
+ ensure
54
+ free(id)
55
+ end
56
+ else
57
+ id
58
+ end
59
+ end
60
+
61
+ def free(id)
62
+ @mutex.lock
63
+ raise unless valid?(id) && allocated?(id)
64
+
65
+ enclosing_range = enclosing_range(id)
66
+ enclosing_range_index = @allocated_ranges.find_index(enclosing_range)
67
+
68
+ if enclosing_range.size == 1
69
+ @allocated_ranges.delete(enclosing_range)
70
+ elsif enclosing_range.first == id
71
+ @allocated_ranges[enclosing_range_index] = (enclosing_range.first.succ..enclosing_range.last)
72
+ elsif enclosing_range.last == id
73
+ @allocated_ranges[enclosing_range_index] = (enclosing_range.first..enclosing_range.last.pred)
74
+ else
75
+ @allocated_ranges[enclosing_range_index] = (enclosing_range.first..id.pred)
76
+ @allocated_ranges.insert(enclosing_range_index.succ, (id.succ..enclosing_range.last))
77
+ end
78
+
79
+ @mutex.unlock
80
+ nil
81
+ end
82
+
83
+ def allocated?(id)
84
+ enclosing_range(id)
85
+ end
86
+
87
+ private
88
+
89
+ def free?(id)
90
+ !allocated?(id)
91
+ end
92
+
93
+ def enclosing_range(id)
94
+ possible_range = @allocated_ranges.find{ |range| range.last >= id }
95
+ return possible_range if possible_range && possible_range.first <= id
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,16 @@
1
+ class Sumac
2
+ class LocalReference < Reference
3
+
4
+ attr_reader :exposed_object
5
+
6
+ def initialize(connection, exposed_id, exposed_object)
7
+ super(connection, exposed_id)
8
+ @exposed_object = exposed_object
9
+ end
10
+
11
+ def remove
12
+ @connection.local_references.remove(self)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,80 @@
1
+ class Sumac
2
+ class LocalReferences
3
+
4
+ def initialize(connection)
5
+ raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
6
+ @connection = connection
7
+ @id_allocator = IDAllocator.new
8
+ @exposed_id_table = {}
9
+ @native_id_table = {}
10
+ @transaction = []
11
+ end
12
+
13
+ def detach
14
+ @exposed_id_table.values.each { |reference| reference.detach }
15
+ end
16
+
17
+ def destroy
18
+ @exposed_id_table.values.each { |reference| reference.destroy }
19
+ end
20
+
21
+ def from_id(exposed_id)
22
+ raise unless @id_allocator.valid?(exposed_id)
23
+ reference = @exposed_id_table[exposed_id]
24
+ reference
25
+ end
26
+
27
+ def from_object(exposed_object)
28
+ raise unless exposed_object.is_a?(ExposedObject)
29
+ reference = find(exposed_object) || create(exposed_object)
30
+ reference
31
+ end
32
+
33
+ def remove(reference)
34
+ @exposed_id_table.delete(reference.exposed_id)
35
+ references = @native_id_table[reference.exposed_object.__native_id__]
36
+ references.delete(reference)
37
+ @native_id_table.delete(reference.exposed_object.__native_id__) if references.empty?
38
+ @id_allocator.free(reference.exposed_id)
39
+ end
40
+
41
+ def rollback_transaction
42
+ @transaction.each { |reference| reference.quiet_forget }
43
+ @transaction = []
44
+ end
45
+
46
+ def commit_transaction
47
+ @transaction = nil
48
+ end
49
+
50
+ def start_transaction
51
+ @transaction = []
52
+ end
53
+
54
+ private
55
+
56
+ def create(exposed_object)
57
+ new_exposed_id = @id_allocator.allocate
58
+ new_reference = LocalReference.new(@connection, new_exposed_id, exposed_object)
59
+ @exposed_id_table[new_exposed_id] = new_reference
60
+ references = @native_id_table[exposed_object.__native_id__]
61
+ if references
62
+ references << new_reference
63
+ else
64
+ @native_id_table[exposed_object.__native_id__] = [new_reference]
65
+ end
66
+ @transaction << new_reference if @transaction
67
+ new_reference
68
+ end
69
+
70
+ def find(exposed_object)
71
+ references = @native_id_table[exposed_object.__native_id__]
72
+ return nil unless references
73
+ callable_references = references.select { |reference| reference.callable? }
74
+ raise if callable_references.length > 1
75
+ reference = callable_references.first
76
+ reference
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ class Sumac
2
+ class Message
3
+
4
+ def self.from_json(connection, json)
5
+ json_structure = JSON.parse(json)
6
+ from_json_structure(connection, json_structure)
7
+ end
8
+
9
+ def initialize(connection)
10
+ raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
11
+ @connection = connection
12
+ end
13
+
14
+ def to_json
15
+ to_json_structure.to_json
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ class Sumac
2
+ class Message
3
+ class Exchange < Message
4
+
5
+ def self.from_json_structure(connection, json_structure)
6
+ raise MessageError unless json_structure.is_a?(Hash) && json_structure['message_type'] == 'exchange'
7
+ exchange_class =
8
+ case json_structure['exchange_type']
9
+ when 'compatibility_notification'
10
+ CompatibilityNotification
11
+ when 'initialization_notification'
12
+ InitializationNotification
13
+ when 'shutdown_notification'
14
+ ShutdownNotification
15
+ when 'forget_notification'
16
+ ForgetNotification
17
+ when 'call_request'
18
+ CallRequest
19
+ when 'call_response'
20
+ CallResponse
21
+ else
22
+ raise MessageError
23
+ end
24
+ exchange = exchange_class.from_json_structure(connection, json_structure)
25
+ exchange
26
+ end
27
+
28
+ end
29
+ end
30
+ end