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,5 @@
1
+ class Sumac
2
+ module Adapter
3
+
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class Sumac
2
+ module Adapter
3
+ class ClosedError < StandardError
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class Sumac
2
+ class ArgumentError < StandardError
3
+
4
+ end
5
+ end
@@ -0,0 +1,70 @@
1
+ class Sumac
2
+ class CallDispatcher
3
+
4
+ def initialize(connection)
5
+ raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
6
+ @connection = connection
7
+ @pending_requests = {}
8
+ @id_allocator = IDAllocator.new
9
+ end
10
+
11
+ def any_calls_pending?
12
+ @pending_requests.any?
13
+ end
14
+
15
+ def kill_all
16
+ raise unless @connection.at?(:kill)
17
+ @pending_requests.each do |id, waiter|
18
+ @pending_requests.delete(id)
19
+ waiter.resume(nil)
20
+ end
21
+ end
22
+
23
+ def make_call(remote_object, method_name, arguments)
24
+ raise unless remote_object.is_a?(RemoteObject) || remote_object.is_a?(RemoteObjectChild)
25
+ raise ClosedError unless @connection.at?(:active)
26
+ id = @id_allocator.allocate
27
+ request = Message::Exchange::CallRequest.new(@connection)
28
+ request.id = id
29
+ @connection.local_references.start_transaction
30
+ @connection.remote_references.start_transaction
31
+ begin
32
+ request.exposed_object = remote_object
33
+ request.method_name = method_name
34
+ request.arguments = arguments
35
+ rescue StandardError => e # MessageError, StaleObjectError
36
+ @connection.local_references.rollback_transaction
37
+ @connection.remote_references.rollback_transaction
38
+ raise e
39
+ else
40
+ @connection.local_references.commit_transaction
41
+ @connection.remote_references.commit_transaction
42
+ end
43
+ @connection.messenger.send(request)
44
+ raise ClosedError if @connection.at?([:kill, :close])
45
+ waiter = QuackConcurrency::Waiter.new
46
+ @pending_requests[id] = waiter
47
+ @connection.mutex.unlock
48
+ response = waiter.wait
49
+ @connection.mutex.lock
50
+ @id_allocator.free(id)
51
+ @connection.closer.job_finished
52
+ raise ClosedError if response == nil
53
+ raise response.exception if response.exception
54
+ response.return_value
55
+ ensure
56
+ @id_allocator.free(id) if id && @id_allocator.allocated?(id)
57
+ end
58
+
59
+ def receive(exchange)
60
+ raise MessageError unless @connection.at?([:active, :initiate_shutdown, :shutdown])
61
+ raise MessageError unless exchange.is_a?(Message::Exchange::CallResponse)
62
+ waiter = @pending_requests[exchange.id]
63
+ @pending_requests.delete(exchange.id)
64
+ raise MessageError unless waiter
65
+ waiter.resume(exchange)
66
+ nil
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,93 @@
1
+ class Sumac
2
+ class CallProcessor
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
+ @pending_calls = 0
9
+ end
10
+
11
+ def any_calls_processing?
12
+ @pending_calls > 0
13
+ end
14
+
15
+ def receive(exchange)
16
+ raise MessageError unless exchange.is_a?(Message::Exchange::CallRequest)
17
+ raise MessageError unless @connection.at?([:active, :initiate_shutdown])
18
+ raise MessageError unless exchange.exposed_object.is_a?(ExposedObject) || exchange.exposed_object.is_a?(ExposedObjectChild)
19
+ response = process(exchange)
20
+ @connection.messenger.send(response) if response
21
+ finished
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ def process(request)
28
+ @pending_calls += 1
29
+ response = Message::Exchange::CallResponse.new(@connection)
30
+ response.id = request.id
31
+ # validate_exposed_object_exist
32
+ begin
33
+ exposed_object = request.exposed_object
34
+ rescue MessageError
35
+ response.exception = MessageError.new
36
+ return response
37
+ end
38
+ # validate_method_exposed
39
+ unless request.exposed_object.__exposed_methods__.include?(request.method_name)
40
+ response.exception = NoMethodError.new
41
+ return response
42
+ end
43
+ # validate_arguments
44
+ @connection.local_references.start_transaction
45
+ @connection.remote_references.start_transaction
46
+ begin
47
+ arguments = request.arguments
48
+ rescue StandardError => e
49
+ response.exception = e
50
+ @connection.local_references.rollback_transaction
51
+ @connection.remote_references.rollback_transaction
52
+ return response
53
+ else
54
+ @connection.local_references.commit_transaction
55
+ @connection.remote_references.commit_transaction
56
+ end
57
+ # call method
58
+ @connection.mutex.unlock
59
+ begin
60
+ return_value = exposed_object.__send__(request.method_name, *arguments)
61
+ rescue StandardError => e
62
+ exception_raised = e
63
+ end
64
+ @connection.mutex.lock
65
+ return if @connection.at?(:kill)
66
+ if exception_raised
67
+ response.exception = exception_raised
68
+ return response
69
+ else
70
+ @connection.local_references.start_transaction
71
+ @connection.remote_references.start_transaction
72
+ begin
73
+ response.return_value = return_value
74
+ rescue StandardError => e # MessageError, StaleObjectError, UnexposableError
75
+ response.exception = e
76
+ @connection.local_references.rollback_transaction
77
+ @connection.remote_references.rollback_transaction
78
+ return response
79
+ else
80
+ @connection.local_references.commit_transaction
81
+ @connection.remote_references.commit_transaction
82
+ end
83
+ end
84
+ return response
85
+ end
86
+
87
+ def finished
88
+ @pending_calls -= 1
89
+ @connection.closer.job_finished
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,5 @@
1
+ class Sumac
2
+ class ClosedError < StandardError
3
+
4
+ end
5
+ end
@@ -0,0 +1,53 @@
1
+ class Sumac
2
+ class Closer
3
+
4
+ def initialize(connection)
5
+ raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
6
+ @connection = connection
7
+ @future = QuackConcurrency::Future.new
8
+ end
9
+
10
+ def job_finished
11
+ try_close if @connection.at?([:shutdown, :kill])
12
+ nil
13
+ end
14
+
15
+ def try_close
16
+ @connection.to(:close) if can_close?
17
+ nil
18
+ end
19
+
20
+ def close
21
+ @connection.mutex.synchronize do
22
+ case @connection.at.to_sym
23
+ when :initial, :compatibility_handshake, :initialization_handshake
24
+ @connection.to(:kill)
25
+ when :active
26
+ @connection.to(:initiate_shutdown)
27
+ end
28
+ end
29
+ @future.get
30
+ @connection.scheduler.join
31
+ nil
32
+ end
33
+
34
+ def complete
35
+ @future.set
36
+ nil
37
+ end
38
+
39
+ def join
40
+ @future.get
41
+ @connection.scheduler.join
42
+ nil
43
+ end
44
+
45
+ private
46
+
47
+ def can_close?
48
+ !@connection.call_dispatcher.any_calls_pending? &&
49
+ !@connection.call_processor.any_calls_processing?
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,94 @@
1
+ class Sumac
2
+ class Connection
3
+ include QueuedStateMachine
4
+
5
+ state :initial, initial: true
6
+ state :compatibility_handshake
7
+ state :initialization_handshake
8
+ state :active
9
+ state :initiate_shutdown
10
+ state :shutdown
11
+ state :kill
12
+ state :close
13
+
14
+ transition from: :initial, to: :compatibility_handshake
15
+ transition from: :compatibility_handshake, to: [:initialization_handshake, :kill]
16
+ transition from: :initialization_handshake, to: [:active, :kill]
17
+ transition from: :active, to: [:initiate_shutdown, :shutdown, :kill]
18
+ transition from: :initiate_shutdown, to: [:shutdown, :kill]
19
+ transition from: :shutdown, to: [:close, :kill]
20
+ transition from: :kill, to: :close
21
+
22
+ on_transition(from: :initial, to: :compatibility_handshake) do
23
+ @handshake.send_compatibility_notification
24
+ end
25
+
26
+ on_transition(from: :compatibility_handshake, to: :initialization_handshake) do
27
+ @handshake.send_initialization_notification
28
+ end
29
+
30
+ on_transition(from: [:compatibility_handshake, :initialization_handshake], to: :kill) do
31
+ @local_references.detach
32
+ @remote_references.detach
33
+ end
34
+
35
+ on_transition(from: :active) do
36
+ @local_references.detach
37
+ @remote_references.detach
38
+ end
39
+
40
+ on_transition(from: :active, to: [:initiate_shutdown, :shutdown]) do
41
+ @shutdown.send_notification
42
+ end
43
+
44
+ on_transition(from: :active, to: [:initiate_shutdown, :shutdown, :kill]) do
45
+ @sumac.trigger(:shutdown)
46
+ end
47
+
48
+ on_transition(to: [:shutdown, :kill]) do
49
+ @closer.try_close
50
+ end
51
+
52
+ on_transition(to: :kill) do
53
+ @call_dispatcher.kill_all
54
+ end
55
+
56
+ on_transition(to: [:kill, :close]) do
57
+ @remote_entry.cancel
58
+ end
59
+
60
+ on_transition(to: :close) do
61
+ @messenger.close
62
+ @local_references.destroy
63
+ @remote_references.destroy
64
+ @closer.complete
65
+ @sumac.trigger(:close)
66
+ end
67
+
68
+ attr_reader :mutex, :sumac, :call_dispatcher, :call_processor,
69
+ :handshake, :shutdown, :local_references, :remote_references,
70
+ :local_entry, :messenger, :remote_entry, :scheduler,
71
+ :closer, :messenger_adapter
72
+
73
+ attr_accessor :remote_entry
74
+
75
+ def initialize(sumac, duck_types: , entry: , messenger: , workers: )
76
+ super()
77
+ @sumac = sumac
78
+ @local_entry = entry
79
+ @messenger_adapter = messenger
80
+ @remote_entry = RemoteEntry.new
81
+ @mutex = Mutex.new
82
+ @messenger = Messenger.new(self)
83
+ @call_dispatcher = CallDispatcher.new(self)
84
+ @call_processor = CallProcessor.new(self)
85
+ @handshake = Handshake.new(self)
86
+ @shutdown = Shutdown.new(self)
87
+ @local_references = LocalReferences.new(self)
88
+ @remote_references = RemoteReferences.new(self)
89
+ @scheduler = Scheduler.new(self, workers)
90
+ @closer = Closer.new(self)
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,131 @@
1
+ class Sumac
2
+ module ExposedObject
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(:@__child_accessor__, @__child_accessor__)
19
+ end
20
+
21
+ attr_reader :__child_accessor__
22
+
23
+ def __exposed_methods__
24
+ @__exposed_methods__ ||= []
25
+ end
26
+
27
+ def child_accessor(method_name = nil)
28
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
29
+ raise ArgumentError, "'child_accessor' expects a method name as a string for symbol"
30
+ end
31
+ @__child_accessor__ = method_name.to_s
32
+ end
33
+
34
+ def expose(*method_names)
35
+ raise ArgumentError, 'at least one argument expected' if method_names.empty?
36
+ unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
37
+ raise 'only symbols or strings expected'
38
+ end
39
+ @__exposed_methods__ ||= []
40
+ @__exposed_methods__.concat(method_names.map(&:to_s))
41
+ end
42
+
43
+ end
44
+
45
+
46
+ module IncludedInstanceMethods
47
+
48
+ def __child__(key)
49
+ raise 'child_accessor not defined' unless __child_accessor__
50
+ __send__(__child_accessor__, key)
51
+ end
52
+
53
+ def __child_accessor__
54
+ @__child_accessor__ || self.class.__child_accessor__
55
+ end
56
+
57
+ def __exposed_methods__
58
+ @__exposed_methods__ ||= []
59
+ @__exposed_methods__ + self.class.__exposed_methods__
60
+ end
61
+
62
+ def __native_id__
63
+ __id__
64
+ end
65
+
66
+ def __sumac_exposed_object__
67
+ end
68
+
69
+ def child_accessor(method_name = nil)
70
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
71
+ raise ArgumentError, "'child_accessor' expects a method name as a string for symbol"
72
+ end
73
+ @__child_accessor__ = method_name.to_s
74
+ end
75
+
76
+ def expose(*method_names)
77
+ raise ArgumentError, 'at least one argument expected' if method_names.empty?
78
+ unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
79
+ raise 'only symbols or strings expected'
80
+ end
81
+ @__exposed_methods__ ||= []
82
+ @__exposed_methods__.concat(method_names.map(&:to_s))
83
+ end
84
+
85
+ end
86
+
87
+
88
+ module ExtendedClassMethods
89
+
90
+ def inherited(base)
91
+ base.instance_variable_set(:@__exposed_methods__, @__exposed_methods__.dup)
92
+ base.instance_variable_set(:@__child_accessor__, @__child_accessor__)
93
+ end
94
+
95
+ def __child__(key)
96
+ raise 'child_accessor not defined' unless @__child_accessor__
97
+ __send__(@__child_accessor__, key)
98
+ end
99
+
100
+ def __exposed_methods__
101
+ @__exposed_methods__ ||= []
102
+ end
103
+
104
+ def __native_id__
105
+ __id__
106
+ end
107
+
108
+ def __sumac_exposed_object__
109
+ end
110
+
111
+ def child_accessor(method_name = nil)
112
+ unless method_name.is_a?(Symbol) || method_name.is_a?(String)
113
+ raise ArgumentError, "'child_accessor' expects a method name as a string for symbol"
114
+ end
115
+ @__child_accessor__ = method_name.to_s
116
+ end
117
+
118
+ def expose(*method_names)
119
+ raise ArgumentError, 'at least one argument expected' if method_names.empty?
120
+ unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
121
+ raise 'only symbols or strings expected'
122
+ end
123
+ @__exposed_methods__ ||= []
124
+ @__exposed_methods__.concat(method_names.map(&:to_s))
125
+ end
126
+
127
+ end
128
+
129
+
130
+ end
131
+ end