parallel_work 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in parallel_work.gemspec
4
+ gemspec
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"
@@ -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,15 @@
1
+ module ParallelWork
2
+ class Message
3
+ class Quit < Message
4
+
5
+ def name
6
+ "QUIT"
7
+ end
8
+
9
+ def has_payload?
10
+ false
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ParallelWork
2
+ class Message
3
+ class Ready < Message
4
+
5
+ def name
6
+ "READY"
7
+ end
8
+
9
+ def has_payload?
10
+ false
11
+ end
12
+
13
+ end
14
+ end
15
+ 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,3 @@
1
+ module ParallelWork
2
+ VERSION = "0.0.1"
3
+ 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
+