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 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
+