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