parallel_work 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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +22 -0
- data/Rakefile +1 -0
- data/example/example.rb +11 -0
- data/lib/parallel_work.rb +15 -0
- data/lib/parallel_work/message.rb +30 -0
- data/lib/parallel_work/message/quit.rb +15 -0
- data/lib/parallel_work/message/ready.rb +15 -0
- data/lib/parallel_work/message/work.rb +27 -0
- data/lib/parallel_work/messaging.rb +113 -0
- data/lib/parallel_work/runner.rb +90 -0
- data/lib/parallel_work/version.rb +3 -0
- data/parallel_work.gemspec +16 -0
- metadata +79 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
parallel_work
|
2
|
+
===========
|
3
|
+
|
4
|
+
Spread work over multiple processes on the same server, with a central work queue.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
gem install parallel_work
|
9
|
+
|
10
|
+
## Example Usage
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require "parallel_work"
|
14
|
+
|
15
|
+
work = (1..400).to_a
|
16
|
+
|
17
|
+
ParallelWork.process work, 8 do |data|
|
18
|
+
puts data
|
19
|
+
sleep rand
|
20
|
+
end
|
21
|
+
|
22
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/example/example.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path "../lib/parallel_work", File.dirname(__FILE__)
|
2
|
+
|
3
|
+
# Turn on REE copy-on-write GC if we're running on REE
|
4
|
+
GC.copy_on_write_friendly = true if GC.respond_to? :copy_on_write_friendly?
|
5
|
+
|
6
|
+
work = (1..400).to_a
|
7
|
+
|
8
|
+
ParallelWork.process work, 8 do |data|
|
9
|
+
puts data
|
10
|
+
sleep rand
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path "./parallel_work/version", File.dirname(__FILE__)
|
2
|
+
require 'socket'
|
3
|
+
require File.expand_path './parallel_work/runner', File.dirname(__FILE__)
|
4
|
+
require File.expand_path './parallel_work/messaging', File.dirname(__FILE__)
|
5
|
+
require File.expand_path './parallel_work/message', File.dirname(__FILE__)
|
6
|
+
|
7
|
+
module ParallelWork
|
8
|
+
|
9
|
+
# @param work [#each]
|
10
|
+
# @params worker [Fixnum] number of worker processes
|
11
|
+
def self.process work, workers, &process_block
|
12
|
+
Runner.new(work.each, process_block).spawn(workers).close_other_sockets.start
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + '/message/*.rb'].each do |file|
|
2
|
+
require file
|
3
|
+
end
|
4
|
+
|
5
|
+
module ParallelWork
|
6
|
+
class Message
|
7
|
+
|
8
|
+
def self.build name
|
9
|
+
class_name = name[0,1].upcase + name[1,name.length].downcase
|
10
|
+
const_get(class_name).new
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
raise "To be implemented in subclass"
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_payload?
|
18
|
+
raise "To be implemented in subclass"
|
19
|
+
end
|
20
|
+
|
21
|
+
def payload
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def payload_length
|
26
|
+
payload ? payload.length : 0
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ParallelWork
|
2
|
+
class Message
|
3
|
+
class Work < Message
|
4
|
+
|
5
|
+
def initialize data=nil
|
6
|
+
@data = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
"WORK"
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_payload?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def payload
|
18
|
+
@data
|
19
|
+
end
|
20
|
+
|
21
|
+
def payload= data
|
22
|
+
@data = data
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module ParallelWork
|
2
|
+
class Messaging
|
3
|
+
|
4
|
+
MESSAGE_TYPE_LENGTH = 5
|
5
|
+
|
6
|
+
# @param [Socket]
|
7
|
+
# @param [ParallelWork::Message]
|
8
|
+
# @return [void]
|
9
|
+
def self.send socket, message
|
10
|
+
Sending.send socket, message
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Socket]
|
14
|
+
# @return [ParallelWork::Message]
|
15
|
+
def self.recv socket
|
16
|
+
Receiving.recv socket
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
class Sending
|
22
|
+
|
23
|
+
def self.send socket, message
|
24
|
+
new(socket, message).send_message
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize socket, message
|
28
|
+
@socket = socket
|
29
|
+
@message = message
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_message
|
33
|
+
send_message_type
|
34
|
+
if @message.has_payload?
|
35
|
+
send_payload_length
|
36
|
+
send_payload
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def send_message_type
|
43
|
+
send "%#{MESSAGE_TYPE_LENGTH}s" % @message.name
|
44
|
+
end
|
45
|
+
|
46
|
+
# payload length sent as 32 bit int.
|
47
|
+
def send_payload_length
|
48
|
+
send [marshalled_payload.length].pack('l')
|
49
|
+
end
|
50
|
+
|
51
|
+
def send_payload
|
52
|
+
send marshalled_payload
|
53
|
+
end
|
54
|
+
|
55
|
+
def send data
|
56
|
+
@socket.send(data, 0)
|
57
|
+
end
|
58
|
+
|
59
|
+
def marshalled_payload
|
60
|
+
@marshalled_payload ||= Marshal.dump(@message.payload)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class Receiving
|
66
|
+
|
67
|
+
def self.recv socket
|
68
|
+
new(socket).recv_message
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize socket
|
72
|
+
@socket = socket
|
73
|
+
end
|
74
|
+
|
75
|
+
def recv_message
|
76
|
+
message_type = recv_message_type.strip
|
77
|
+
message = build_message message_type
|
78
|
+
if message.has_payload?
|
79
|
+
payload_length = recv_payload_length
|
80
|
+
marshalled_payload = recv_payload payload_length
|
81
|
+
unmarshalled_payload = unmarshall_payload marshalled_payload
|
82
|
+
message.payload = unmarshalled_payload
|
83
|
+
end
|
84
|
+
message
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def recv_message_type
|
90
|
+
@socket.recv(MESSAGE_TYPE_LENGTH)
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_message message_type
|
94
|
+
Message.build(message_type)
|
95
|
+
end
|
96
|
+
|
97
|
+
def recv_payload_length
|
98
|
+
data = @socket.recv(4)
|
99
|
+
data.unpack('l')[0]
|
100
|
+
end
|
101
|
+
|
102
|
+
def recv_payload payload_length
|
103
|
+
@socket.recv(payload_length)
|
104
|
+
end
|
105
|
+
|
106
|
+
def unmarshall_payload marshalled_payload
|
107
|
+
Marshal.load(marshalled_payload)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ParallelWork
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
SOCKET_MAX_LEN = 1000
|
5
|
+
MESSAGE_LEN = 5
|
6
|
+
|
7
|
+
# @param work [#next]
|
8
|
+
def initialize work, process_block
|
9
|
+
@work = work
|
10
|
+
@process_block = process_block
|
11
|
+
@master = nil
|
12
|
+
@parent_sockets = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup_sockets
|
16
|
+
@child_socket, parent_socket = Socket.pair("AF_UNIX", "SOCK_DGRAM", 0)
|
17
|
+
@parent_sockets << parent_socket
|
18
|
+
end
|
19
|
+
|
20
|
+
def spawn n_workers
|
21
|
+
setup_sockets
|
22
|
+
|
23
|
+
child_pid = fork
|
24
|
+
if child_pid
|
25
|
+
if n_workers == 1
|
26
|
+
@master = true
|
27
|
+
else
|
28
|
+
@master = false
|
29
|
+
spawn n_workers - 1
|
30
|
+
end
|
31
|
+
else
|
32
|
+
@master = false
|
33
|
+
end
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def close_other_sockets
|
39
|
+
if @master
|
40
|
+
@child_socket.close
|
41
|
+
else
|
42
|
+
@parent_sockets.each do |socket|
|
43
|
+
socket.close
|
44
|
+
end
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def start
|
50
|
+
if @master
|
51
|
+
puts "master pid #{Process.pid}"
|
52
|
+
its_over = false
|
53
|
+
while !@parent_sockets.empty? && (ready = IO.select(@parent_sockets))
|
54
|
+
socket = ready[0][0]
|
55
|
+
message_from_worker = Messaging.recv socket
|
56
|
+
send_quit = lambda do |socket|
|
57
|
+
Messaging.send socket, Message::Quit.new
|
58
|
+
socket.close
|
59
|
+
end
|
60
|
+
begin
|
61
|
+
if its_over
|
62
|
+
send_quit[socket]
|
63
|
+
else
|
64
|
+
Messaging.send socket, Message::Work.new(@work.next)
|
65
|
+
end
|
66
|
+
rescue StopIteration
|
67
|
+
its_over = true
|
68
|
+
send_quit[socket]
|
69
|
+
end
|
70
|
+
@parent_sockets = @parent_sockets.reject{|s|s.closed?}
|
71
|
+
end
|
72
|
+
else
|
73
|
+
puts "child pid #{Process.pid}"
|
74
|
+
Messaging.send @child_socket, Message::Ready.new
|
75
|
+
while (message = Messaging.recv @child_socket)
|
76
|
+
case message
|
77
|
+
when Message::Work
|
78
|
+
@process_block.call(message.payload)
|
79
|
+
Messaging.send(@child_socket, Message::Ready.new)
|
80
|
+
when Message::Quit
|
81
|
+
exit 0
|
82
|
+
else
|
83
|
+
raise "unknown message #{message}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "parallel_work/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "parallel_work"
|
7
|
+
s.version = ParallelWork::VERSION
|
8
|
+
s.authors = ["Joel Plane"]
|
9
|
+
s.email = ["joel.plane@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/joelplane/parallel_work"
|
11
|
+
s.summary = %q{Spread work over multiple processes}
|
12
|
+
s.description = %q{When work is in a simple queue, this library allows easily spreading the work over multiple processes on the same server communicating with UNIX sockets. Only tested on Ruby 1.8.7 (REE)}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: parallel_work
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Joel Plane
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-01-30 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: When work is in a simple queue, this library allows easily spreading the work over multiple processes on the same server communicating with UNIX sockets. Only tested on Ruby 1.8.7 (REE)
|
22
|
+
email:
|
23
|
+
- joel.plane@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- README.md
|
34
|
+
- Rakefile
|
35
|
+
- example/example.rb
|
36
|
+
- lib/parallel_work.rb
|
37
|
+
- lib/parallel_work/message.rb
|
38
|
+
- lib/parallel_work/message/quit.rb
|
39
|
+
- lib/parallel_work/message/ready.rb
|
40
|
+
- lib/parallel_work/message/work.rb
|
41
|
+
- lib/parallel_work/messaging.rb
|
42
|
+
- lib/parallel_work/runner.rb
|
43
|
+
- lib/parallel_work/version.rb
|
44
|
+
- parallel_work.gemspec
|
45
|
+
homepage: https://github.com/joelplane/parallel_work
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.7.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Spread work over multiple processes
|
78
|
+
test_files: []
|
79
|
+
|