shatter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|