shatter 0.0.1
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.
- checksums.yaml +7 -0
- data/lib/shatter.rb +163 -0
- data/lib/shatter/controller.rb +167 -0
- data/lib/shatter/pid.rb +26 -0
- data/lib/shatter/pidlist.rb +60 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 21861829b74cc78af7c6dc8db6b9bab8fd13e87c
|
4
|
+
data.tar.gz: 57b5858ca1af53a8c035d83c818f46df455409ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb13accb271cb65b0da9b641544b46131cbef0c2a97f3332e0ad841126aa8e5005058432f39c196db168cf910c70225ffcb3f54ed1824bbd7df915588c102f43
|
7
|
+
data.tar.gz: 6b54e5c27425d2c25f3fa4bb898a5425c6c8a38635d82d273de626417f7ec9b6141ee0084e57d117c4971e2b3b0bef8ba81d4c52b415d35b02cbc3085930f563
|
data/lib/shatter.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'funtools'
|
2
|
+
require 'shatter/controller'
|
3
|
+
|
4
|
+
module Shatter
|
5
|
+
extend self
|
6
|
+
VERSION = '0.0.1'
|
7
|
+
PORTRANGE = 9479..9749
|
8
|
+
|
9
|
+
controller = nil
|
10
|
+
set = ->(new_controller) { controller ||= new_controller }
|
11
|
+
|
12
|
+
# Public: Set the Controller for the currently running process. This will be
|
13
|
+
# used directly if the current process is expected to be passed messages.
|
14
|
+
#
|
15
|
+
# parent - Pid to which to report the process's new Pid. (default: nil)
|
16
|
+
#
|
17
|
+
# Returns nothing.
|
18
|
+
define_method(:init) { |parent=nil| set.(Controller.new(parent)) }
|
19
|
+
|
20
|
+
# Public: Clear the process's Controller, then call init to set a new one.
|
21
|
+
# This is needed after calling fork in order to obtain a new socket.
|
22
|
+
#
|
23
|
+
# parent - Pid representing the parent process from which the new process has
|
24
|
+
# been forked.
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
define_method(:chip) { |parent| controller=nil; init(parent) }
|
28
|
+
|
29
|
+
# Public: Wrap Controller#receive, passing a given block to the current
|
30
|
+
# Controller.
|
31
|
+
#
|
32
|
+
# block - Proc which will process messages passed to it from the Controller's
|
33
|
+
# mailbox.
|
34
|
+
#
|
35
|
+
# Returns the result of block being passed messages from Controller's mailbox.
|
36
|
+
define_method(:receive) { |&block| controller.receive(&block) }
|
37
|
+
|
38
|
+
# Public: Call a given method on a Module, passing in given arguments.
|
39
|
+
#
|
40
|
+
# mod - Module which contains the method referenced by fun.
|
41
|
+
# fun - Symbol or String referencing a method on the Module provided.
|
42
|
+
# args - Arguments to be passed to the method.
|
43
|
+
#
|
44
|
+
# Returns the result of invoking fun(*args) on mod.
|
45
|
+
define_method(:mfa) { |mod, fun, args| mod.send(fun, *args) }
|
46
|
+
|
47
|
+
# Public - Fetch the current Controllers pid if it is set.
|
48
|
+
#
|
49
|
+
# Returns a Pid or else nil.
|
50
|
+
define_method(:pid) { controller.pid if controller }
|
51
|
+
|
52
|
+
# Public - Return the Pid of the process which spawned the current process if
|
53
|
+
# one exists.
|
54
|
+
#
|
55
|
+
# Returns a Pid or else nil.
|
56
|
+
define_method(:parent) { controller.parent if controller }
|
57
|
+
end
|
58
|
+
|
59
|
+
module Kernel
|
60
|
+
private
|
61
|
+
|
62
|
+
# Public: Fork a new process to perform a given job.
|
63
|
+
#
|
64
|
+
# mod - Module which contains the method referenced by fun.
|
65
|
+
# fun - Symbol or String referencing a method on the Module provided.
|
66
|
+
# args - Arguments to be passed to the method.
|
67
|
+
#
|
68
|
+
# Returns nothing.
|
69
|
+
def chip(mod, fun, *args)
|
70
|
+
forked = fork do
|
71
|
+
Shatter.chip(Shatter.pid)
|
72
|
+
Shatter.mfa(mod, fun, args)
|
73
|
+
end
|
74
|
+
Process::detach(forked)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: Return the current Controller's Pid if applicable.
|
78
|
+
#
|
79
|
+
# Returns a Pid or else nil.
|
80
|
+
def pid
|
81
|
+
Shatter.pid
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Wrap Pid#pass.
|
85
|
+
#
|
86
|
+
# Returns nothing.
|
87
|
+
def pass(target, *messages)
|
88
|
+
target.pass(*messages)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: Return the current Controller's Parent's Pid if applicable.
|
92
|
+
#
|
93
|
+
# Returns a Pid or else nil.
|
94
|
+
def parent
|
95
|
+
Shatter.parent
|
96
|
+
end
|
97
|
+
|
98
|
+
match_proc = ->(a, b) do
|
99
|
+
if([a,b].map { |o| o.is_a?(Enumerable) && a.class == b.class }.all?)
|
100
|
+
raise ArgumentError unless a.length == b.length
|
101
|
+
|
102
|
+
zipped = a.is_a?(Hash) ? a.sort.zip(b.sort) : a.zip(b)
|
103
|
+
|
104
|
+
zipped.reduce(true) do |c, e|
|
105
|
+
c && match.(*e)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
a.nil? || a == b
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Public: Define a pattern to be matched within the context of a receive
|
113
|
+
# block, providing a block to execute when pattern is matched.
|
114
|
+
#
|
115
|
+
# l - Any number of arguments to define a pattern against which to match.
|
116
|
+
# b - Proc to pass the arguments to if all pattern matches succeed.
|
117
|
+
#
|
118
|
+
# Raises SyntaxError if the thread is not called within the context of a
|
119
|
+
# receive block (as signified by the thread variable :matches).
|
120
|
+
#
|
121
|
+
# Returns nothing.
|
122
|
+
define_method :match do |*l, &b|
|
123
|
+
unless Thread.current[:matches].is_a?(Array)
|
124
|
+
raise(SyntaxError, 'syntax error, unexpected match')
|
125
|
+
end
|
126
|
+
|
127
|
+
Thread.current[:matches] << ->(n) do
|
128
|
+
->(*m) do
|
129
|
+
if m.length == n.length
|
130
|
+
raise NoMatch unless m.zip(n).map { |e| match_proc.(*e) }.all?
|
131
|
+
instance_exec(*n, &b)
|
132
|
+
end
|
133
|
+
end.(*l)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Public: Build a pattern list and wrap Controller#receive, receiving items
|
138
|
+
# from the Controller's mailbox and responding to them as appropriate.
|
139
|
+
#
|
140
|
+
# block - Proc containing pattern definitons.
|
141
|
+
#
|
142
|
+
# Returns nothing.
|
143
|
+
def receive(&block)
|
144
|
+
old_matches = Thread.current[:matches]
|
145
|
+
Thread.current[:matches] = []
|
146
|
+
|
147
|
+
yield
|
148
|
+
|
149
|
+
# item is always an array due to the way Controller#receive and Pid#pass
|
150
|
+
# work
|
151
|
+
Shatter.receive do |item|
|
152
|
+
Thread.current[:matches].each do |pattern|
|
153
|
+
begin
|
154
|
+
return instance_exec(item, &pattern)
|
155
|
+
rescue NoMatch
|
156
|
+
end
|
157
|
+
end
|
158
|
+
instance_exec { raise NoMatch }
|
159
|
+
end
|
160
|
+
|
161
|
+
Thread::current[:matches] = old_matches
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'shatter/pid'
|
2
|
+
require 'shatter/pidlist'
|
3
|
+
require 'socket'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
module Shatter
|
7
|
+
class Controller
|
8
|
+
attr_reader :parent
|
9
|
+
|
10
|
+
# Public: Initialize the Controller, setting up the listening Socket and
|
11
|
+
# thread to manage the mailbox.
|
12
|
+
#
|
13
|
+
# parent - Pid of the parent Controller, if applicable.
|
14
|
+
def initialize(parent)
|
15
|
+
@parent = parent
|
16
|
+
@socket = listen
|
17
|
+
@mailbox = Queue.new
|
18
|
+
@known = Shatter::Pidlist.new
|
19
|
+
@chunks = {}
|
20
|
+
|
21
|
+
pass(@parent, :system, [:childpid, pid]) if @parent
|
22
|
+
Thread.new { mailbox_loop }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Pass items in the current mailbox to a given block, removing them
|
26
|
+
# permanently from the mailbox unless the item doesn't match any pattern in
|
27
|
+
# the block.
|
28
|
+
#
|
29
|
+
# Returns nothing.
|
30
|
+
def receive(&block)
|
31
|
+
newbox = Queue.new
|
32
|
+
item = @mailbox.pop
|
33
|
+
instance_exec { block.(item) }
|
34
|
+
rescue NoMatch
|
35
|
+
newbox << item
|
36
|
+
retry
|
37
|
+
ensure
|
38
|
+
restore_mailbox(@mailbox, newbox, nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Return the Pid representing the Controller, initializing the
|
42
|
+
# struct if necessary.
|
43
|
+
#
|
44
|
+
# Returns a Pid.
|
45
|
+
def pid
|
46
|
+
unless @pid
|
47
|
+
_, port, _, ip = @socket.addr.map(&:freeze)
|
48
|
+
@pid = Shatter::Pid.new($$, ip, port, '')
|
49
|
+
end
|
50
|
+
@pid
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Internal: Accept connections on the listening socket, accepting messages
|
56
|
+
# and putting them into the mailbox. This should be run within its own
|
57
|
+
# thread.
|
58
|
+
#
|
59
|
+
# Does not return.
|
60
|
+
deftail :mailbox_loop do
|
61
|
+
connection = @socket.accept
|
62
|
+
Thread.new do
|
63
|
+
data = connection.read
|
64
|
+
begin
|
65
|
+
message = Marshal.load(data)
|
66
|
+
@mailbox << message unless handle_message(message)
|
67
|
+
rescue ArgumentError
|
68
|
+
end
|
69
|
+
end
|
70
|
+
mailbox_loop
|
71
|
+
end
|
72
|
+
|
73
|
+
# Internal: Open a listening socket on the first available port within a
|
74
|
+
# given range.
|
75
|
+
#
|
76
|
+
# socket - TCPServer if one has been established, or nil. (default: nil)
|
77
|
+
# range - Range of ports to try to bind to. (default: 9479..9749)
|
78
|
+
# port - Fixnum indicating the port to attempt to bind to.
|
79
|
+
# (default: lowest port in range)
|
80
|
+
#
|
81
|
+
# Returns a listening TCPSocket.
|
82
|
+
deftail :listen do |socket=nil, range=PORTRANGE, port=range.min|
|
83
|
+
socket ? socket : listen(get_socket(port, range.max), range, port+1)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal: Restore the state of a mailbox from a mailbox and temporary
|
87
|
+
# mailbox, first emptying the mailbox into the temporary queue and then
|
88
|
+
# vise versa, keeping the order consistent.
|
89
|
+
#
|
90
|
+
# a - Queue representing the original mailbox.
|
91
|
+
# b - Queue representing the temporary target mailbox.
|
92
|
+
# c - Status indication - true indicating that the original mailbox is
|
93
|
+
# ready to be filled, with any other value indicating that the original
|
94
|
+
# mailbox is still in need of being emptied.
|
95
|
+
#
|
96
|
+
# Returns nothing.
|
97
|
+
defpattern :restore_mailbox do
|
98
|
+
restore_mailbox(nil, nil, true) do |a,b,_|
|
99
|
+
drain_mailbox(a, b)
|
100
|
+
end
|
101
|
+
restore_mailbox(nil, nil, nil) do |a,b,_|
|
102
|
+
restore_mailbox(b, a, drain_mailbox(a, b))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Internal: Check for any messages which are meant to be handled by the
|
107
|
+
# system itself (indicated by [:system, [:type, payload]] structure).
|
108
|
+
# Handle any which are encountered, or else return false. Handlers must
|
109
|
+
# return a truthy value to avoid the system message being added to the
|
110
|
+
# mailbox.
|
111
|
+
#
|
112
|
+
# message - Message to be checked.
|
113
|
+
#
|
114
|
+
# Returns false unless a message is handled.
|
115
|
+
defpattern :handle_message do
|
116
|
+
handle_message([:system, [:childpid, nil]]) { |_, pid| add_pid(pid.last) }
|
117
|
+
handle_message(nil) { |_| false }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Internal: Add a Pid of another Controller to a list of known Pids.
|
121
|
+
#
|
122
|
+
# pid - Pid representing another Controller.
|
123
|
+
#
|
124
|
+
# Returns the Pid.
|
125
|
+
def add_pid(pid)
|
126
|
+
@known.insert(pid)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Internal: Remove all messages from a given source Queue, inserting them
|
130
|
+
# into a target Queue.
|
131
|
+
#
|
132
|
+
# source - Queue from which to drain messages.
|
133
|
+
# target - Queue into which messages should be pushed.
|
134
|
+
#
|
135
|
+
# Returns true once source is empty.
|
136
|
+
def drain_mailbox(source, target)
|
137
|
+
loop { target << source.pop(true) }
|
138
|
+
rescue ThreadError
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
# Internal: Attempt to start a TCPServer on a given port.
|
143
|
+
#
|
144
|
+
# port - Fixnum indicating the port to which to attempt to bind.
|
145
|
+
# max - Fixnum representing the maximum port allowable.
|
146
|
+
#
|
147
|
+
# Raises StandardError if port is greater than max.
|
148
|
+
#
|
149
|
+
# Returns a TCPServer if bound, or nil if the port is in use.
|
150
|
+
def get_socket(port, max)
|
151
|
+
raise StandardError, "Cannot allocate a port" if port > max
|
152
|
+
|
153
|
+
TCPServer.open('127.0.0.1', port)
|
154
|
+
rescue Errno::EADDRINUSE
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
|
158
|
+
# listen :: Maybe Socket -> Range -> Fixnum -> Socket
|
159
|
+
settype(:listen, [IPSocket, NilClass], Range, Fixnum, IPSocket)
|
160
|
+
# restore_mailbox :: Queue -> Queue -> Bool -> Bool
|
161
|
+
settype(:restore_mailbox, Queue, Queue, [TrueClass, NilClass], TrueClass)
|
162
|
+
# drain_mailbox :: Queue -> Queue -> Bool
|
163
|
+
settype(:drain_mailbox, Queue, Queue, TrueClass)
|
164
|
+
# get_socket :: Fixnum -> Fixnum -> Maybe Socket
|
165
|
+
settype(:get_socket, Fixnum, Fixnum, [NilClass, IPSocket])
|
166
|
+
end
|
167
|
+
end
|
data/lib/shatter/pid.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Shatter
|
4
|
+
class Pid < Struct.new(:pid, :host, :port, :name)
|
5
|
+
# Public: Open a socket to a given Controller and send any number of
|
6
|
+
# messages to it.
|
7
|
+
#
|
8
|
+
# messages - Any number of arguments which will constitute a message to be
|
9
|
+
# sent to the Controller.
|
10
|
+
#
|
11
|
+
# Returns nothing.
|
12
|
+
def pass(*messages)
|
13
|
+
socket = TCPSocket.new(host, port)
|
14
|
+
socket.send(Marshal.dump(messages), 0)
|
15
|
+
socket.close
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Represent the Pid as a String.
|
19
|
+
#
|
20
|
+
# Returns a String.
|
21
|
+
def inspect
|
22
|
+
"<#{pid} #{host}:#{port}#{" (#{name})" unless name.empty?}>"
|
23
|
+
end
|
24
|
+
alias :to_s :inspect
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'funtools'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Shatter
|
5
|
+
class Pidlist
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# Public: Initialize the internal state of the Pidlist.
|
9
|
+
def initialize
|
10
|
+
@known = []
|
11
|
+
@mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Iterate through known Pids.
|
15
|
+
#
|
16
|
+
# Yields each item in the list of known Pids.
|
17
|
+
#
|
18
|
+
# Returns nothing.
|
19
|
+
def each(&block)
|
20
|
+
@known.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Add a Pid to the list of known Pids.
|
24
|
+
#
|
25
|
+
# pid - Pid to be tracked.
|
26
|
+
#
|
27
|
+
# Returns nothing.
|
28
|
+
def insert(pid)
|
29
|
+
mutex.synchronize do
|
30
|
+
known << pid
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Internal: Create a method which allows for fetching a Pid by a given
|
35
|
+
# attribute.
|
36
|
+
#
|
37
|
+
# Signature
|
38
|
+
#
|
39
|
+
# by_<kind>
|
40
|
+
#
|
41
|
+
# kind - String containing the name of the value by which to select.
|
42
|
+
def self.build_fetch_by(kind)
|
43
|
+
define_method("by_#{kind}") do |val|
|
44
|
+
select { |pid| pid.send(kind) == val }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
build_fetch_by('name')
|
49
|
+
build_fetch_by('pid')
|
50
|
+
build_fetch_by('host')
|
51
|
+
build_fetch_by('port')
|
52
|
+
|
53
|
+
# insert :: Pid -> [Pid]
|
54
|
+
settype(:insert, Pid, Array)
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :known, :mutex
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shatter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tina Wuest
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: funtools
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.7'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.7.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.7'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.7.1
|
33
|
+
description: Framework to facilitate distributed computing with Ruby
|
34
|
+
email: tina@wuest.me
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- lib/shatter.rb
|
40
|
+
- lib/shatter/controller.rb
|
41
|
+
- lib/shatter/pid.rb
|
42
|
+
- lib/shatter/pidlist.rb
|
43
|
+
homepage: https://gitlab.com/wuest/shatter
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.2.2
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Distributed ruby library
|
67
|
+
test_files: []
|