thread_tools 0.23

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Daniel Tralamazza <daniel@tralamazza.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = thread_tools
2
+
3
+ * http://github.com/differential/thread_tools
4
+
5
+ == DESCRIPTION:
6
+
7
+ A collection of utilities that I often use for threaded code.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Thread pool
12
+ * Mongrel patch (thread pool)
13
+ * Semaphore
14
+ * Debug mutex
15
+
16
+ == SYNOPSIS:
17
+
18
+ * Bounded thread pool
19
+ require 'thread_tools/threadpool'
20
+
21
+ tpool = ThreadTools::ThreadPool.new(2)
22
+ 10.times {|i|
23
+ tpool.spawn(i) {|ti|
24
+ puts "#{Thread.current} => #{ti}"
25
+ }
26
+ }
27
+ tpool.shutdown
28
+
29
+ * Stupid simple semaphore
30
+ require 'thread_tools/semaphore'
31
+
32
+ sem = ThreadTools::Semaphore.new(1)
33
+ sem.acquire
34
+ sem.release
35
+
36
+ * Thread pool for Mongrel
37
+ require 'mongrel'
38
+ require 'thread_tools/mongrel_pool'
39
+
40
+ server = Mongrel::HttpServer.new(host, port)
41
+ server.run(10).join
42
+
43
+ == REQUIREMENTS:
44
+
45
+ * Mongrel patch requires mongrel (duh)
46
+ * Rake if you want to run the test cases
47
+
48
+ == INSTALL:
49
+
50
+ gem install thread_tools
51
+ or
52
+ gem build thread_tools.gemspec
53
+ gem install thread_tools-<version>.gem
54
+
55
+ == LICENSE:
56
+
57
+ see LICENSE
@@ -0,0 +1,56 @@
1
+ # Author: Daniel Tralamazza
2
+ # Date: 29 Sep 2009
3
+ #
4
+ # Mutex
5
+ #
6
+ # This class will make things slower, expect more contentions than normal
7
+ # I derived this mutex because I needed to check contention levels and trace
8
+ # owner changes
9
+
10
+ require 'thread'
11
+
12
+
13
+ module ThreadTools
14
+
15
+ class DebugMutex < Mutex
16
+ attr_reader :contentions
17
+ attr_reader :owner
18
+
19
+ def initialize
20
+ @contentions = 0
21
+ end
22
+
23
+ def lock
24
+ unless try_lock
25
+ super
26
+ @owner = Thread.current
27
+ end
28
+ self
29
+ end
30
+
31
+ def try_lock
32
+ ret = super
33
+ unless (ret)
34
+ @contentions += 1
35
+ else
36
+ @owner = Thread.current
37
+ end
38
+ ret
39
+ end
40
+
41
+ def unlock
42
+ @owner = nil
43
+ super
44
+ end
45
+
46
+ def synchronize
47
+ lock
48
+ begin
49
+ yield
50
+ ensure
51
+ unlock
52
+ end
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,76 @@
1
+ # Author: Daniel Tralamazza
2
+ # Date: 28 Sep 2009
3
+ #
4
+ # This code patches mongrel to use a thread pool instead of creating a new thread
5
+ # for every request. It also traps SIGTERM to close all connections
6
+ # Changed method run to accept an extra parameter to set thread pool size
7
+ #
8
+
9
+
10
+ require File.expand_path(File.dirname(__FILE__)+'/threadpool')
11
+
12
+
13
+ module Mongrel
14
+ class HttpServer
15
+
16
+ def run(_pool_size = 100)
17
+ trap("TERM") { stop } # trap "kill"
18
+
19
+ @thread_pool = ThreadTools::ThreadPool.new(_pool_size, @workers)
20
+
21
+ BasicSocket.do_not_reverse_lookup = true
22
+
23
+ configure_socket_options
24
+
25
+ if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts
26
+ @socket.setsockopt(*$tcp_defer_accept_opts) rescue nil
27
+ end
28
+
29
+ @acceptor = Thread.new do
30
+ begin
31
+ while true
32
+ begin
33
+ client = @socket.accept
34
+
35
+ if defined?($tcp_cork_opts) and $tcp_cork_opts
36
+ client.setsockopt(*$tcp_cork_opts) rescue nil
37
+ end
38
+
39
+ worker_list = @workers.list
40
+
41
+ if worker_list.length >= @num_processors
42
+ STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
43
+ client.close rescue nil
44
+ reap_dead_workers("max processors")
45
+ else
46
+ @thread_pool.spawn(client) do |c|
47
+ Thread.current[:started_on] = Time.now
48
+ process_client(c)
49
+ end
50
+ sleep @throttle if @throttle > 0
51
+ end
52
+ rescue StopServer
53
+ break
54
+ rescue Errno::EMFILE
55
+ reap_dead_workers("too many open files")
56
+ sleep 0.5
57
+ rescue Errno::ECONNABORTED
58
+ # client closed the socket even before accept
59
+ client.close rescue nil
60
+ rescue Object => e
61
+ STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
62
+ STDERR.puts e.backtrace.join("\n")
63
+ end
64
+
65
+ end
66
+ @thread_pool.shutdown
67
+ graceful_shutdown
68
+ ensure
69
+ @socket.close
70
+ # STDERR.puts "#{Time.now}: Closed socket."
71
+ end
72
+ end
73
+ @acceptor
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,39 @@
1
+ # Author: Daniel Tralamazza
2
+ # Date:
3
+ #
4
+ # Semaphore
5
+ #
6
+
7
+
8
+ require 'thread'
9
+
10
+
11
+ module ThreadTools
12
+
13
+ # Poor man's semaphore
14
+ class Semaphore
15
+ attr_reader :count
16
+ def initialize(_count)
17
+ @count = _count
18
+ @mtx = Mutex.new
19
+ @cv = ConditionVariable.new
20
+ end
21
+
22
+ def acquire
23
+ @mtx.synchronize do
24
+ @cv.wait(@mtx) until @count > 0
25
+ @count -= 1
26
+ end
27
+ @count
28
+ end
29
+
30
+ def release
31
+ @mtx.synchronize do
32
+ @count += 1
33
+ @cv.signal
34
+ end
35
+ @count
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,101 @@
1
+ # Author: Daniel Tralamazza
2
+ # Date: 25 Sep 2009
3
+ #
4
+ # ThreadPool
5
+ #
6
+ # Usage:
7
+ #
8
+ # tpool = ThreadTools::ThreadPool.new(3)
9
+ # 12.times do |i|
10
+ # tpool.spawn(i, "hi") {|ti, ts|
11
+ # puts "#{Thread.current} (#{ti}) says #{ts}\n"
12
+ # }
13
+ # end
14
+ # tpool.shutdown
15
+
16
+
17
+ require 'thread'
18
+ require File.expand_path(File.dirname(__FILE__)+'/semaphore')
19
+
20
+
21
+ module ThreadTools
22
+
23
+ class ThreadPool
24
+ # kill the worker thread if an excpetion is raised (default false)
25
+ attr_accessor :kill_worker_on_exception
26
+ # number of worker threads
27
+ attr_reader :size
28
+
29
+ # _size should be at least 1
30
+ def initialize(_size, _thr_group = nil)
31
+ @kill_worker_on_exception = false
32
+ @size = 0
33
+ @pool_mtx = Mutex.new
34
+ @pool_cv = ConditionVariable.new
35
+ @pool = []
36
+ @thr_grp = _thr_group
37
+ _size = 1 if _size < 1
38
+ _size.times { create_worker }
39
+ end
40
+
41
+ def create_worker
42
+ Thread.new do
43
+ thr = Thread.current
44
+ @pool_mtx.synchronize do
45
+ @size += 1
46
+ if (!@thr_grp.nil?)
47
+ @thr_grp.add(thr)
48
+ end
49
+ end
50
+ thr[:jobs] = [] # XXX array not really necessary
51
+ thr[:sem] = Semaphore.new(0)
52
+ loop do
53
+ @pool_mtx.synchronize do
54
+ @pool << thr # puts this thread in the pool
55
+ @pool_cv.signal
56
+ end
57
+ thr[:sem].acquire # wait here for jobs to become available
58
+ job = thr[:jobs].shift # pop out a job
59
+ if (job.nil? || job[:block].nil?)
60
+ break # exit thread if job or block is nil
61
+ end
62
+ begin
63
+ job[:block].call(*job[:args]) # call block
64
+ rescue
65
+ if (@kill_worker_on_exception)
66
+ break # exit thread on exception
67
+ end
68
+ end
69
+ end
70
+ @pool_mtx.synchronize do
71
+ @size -= 1
72
+ end
73
+ end
74
+ end
75
+
76
+ def spawn(*args, &block)
77
+ thr = nil
78
+ @pool_mtx.synchronize do
79
+ # wait here until a worker is available
80
+ @pool_cv.wait(@pool_mtx) until !(thr = @pool.shift).nil?
81
+ thr[:jobs] << { :args => args, :block => block }
82
+ thr[:sem].release
83
+ end
84
+ thr
85
+ end
86
+
87
+ def shutdown
88
+ thr = nil
89
+ while !@pool.empty? do
90
+ @pool_mtx.synchronize do
91
+ @pool_cv.wait(@pool_mtx) until !(thr = @pool.shift).nil?
92
+ end
93
+ thr[:jobs].clear # clear any pending job
94
+ thr[:jobs] << nil # queue a nil job
95
+ thr[:sem].release
96
+ thr.join # wait here for the thread to die
97
+ end
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,45 @@
1
+ require 'test/unit'
2
+ require 'mongrel'
3
+ require File.expand_path(File.dirname(__FILE__)+'/../lib/thread_tools/mongrel_pool')
4
+ require 'net/http'
5
+
6
+
7
+ class MongrelPoolTest < Test::Unit::TestCase
8
+ class MyHandler < Mongrel::HttpHandler
9
+ attr_reader :uniq_threads
10
+ def initialize()
11
+ super
12
+ @uniq_threads = {}
13
+ end
14
+ def process(req, res)
15
+ @uniq_threads[Thread.current] = true
16
+ res.start do |head,out|
17
+ head["Content-Type"] = "text/html; charset=\"utf-8\""
18
+ out << "test"
19
+ end
20
+ end
21
+ end
22
+
23
+ def setup
24
+ super
25
+ @server = Mongrel::HttpServer.new("127.0.0.1", 18881)
26
+ @handler = MyHandler.new
27
+ @server.register("/", @handler)
28
+ @acceptor = @server.run(2)
29
+ end
30
+
31
+ def test_send_recv_shutdown
32
+ http = Net::HTTP.new("localhost", 18881)
33
+ 4.times {
34
+ headers, body = http.get("/")
35
+ # received correct response
36
+ assert_equal(headers.code, "200")
37
+ assert_equal(body, "test")
38
+ }
39
+ # number of unique threads
40
+ assert_equal(@handler.uniq_threads.size, 2)
41
+ @server.stop(true)
42
+ # all workers are dead
43
+ assert_equal(@server.workers.list.size, 0)
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.dirname(__FILE__)+'/../lib/thread_tools/semaphore')
3
+
4
+
5
+ class SemaphoreTest < Test::Unit::TestCase
6
+ def test_acquire_release
7
+ sem = ThreadTools::Semaphore.new(1)
8
+ # should be able to acquire
9
+ assert_equal(sem.acquire, 0)
10
+ ok = false
11
+ Thread.new do
12
+ # should block here
13
+ sem.acquire
14
+ # and unblock
15
+ ok = true
16
+ end
17
+ Thread.pass
18
+ sem.release
19
+ sleep 0.1
20
+ # release should unblock acquire
21
+ assert_equal(ok, true)
22
+ # semaphore count has to be 0
23
+ assert_equal(sem.count, 0)
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ require 'test/unit'
2
+ require File.expand_path(File.dirname(__FILE__)+'/../lib/thread_tools/threadpool')
3
+
4
+
5
+ class ThreadPoolTest < Test::Unit::TestCase
6
+ def setup
7
+ super
8
+ @tpool = ThreadTools::ThreadPool.new(2)
9
+ @tpool.kill_worker_on_exception = true
10
+ end
11
+
12
+ def test_thread_spawn
13
+ uniq_threads = {}
14
+ sum_orig = 0
15
+ sum_lock = Mutex.new
16
+ sum_thr = 0
17
+ 4.times {|i|
18
+ sum_orig += i
19
+ @tpool.spawn(i) {|ti|
20
+ sum_lock.synchronize do
21
+ sum_thr += ti
22
+ end
23
+ uniq_threads[Thread.current] = true
24
+ if (ti == 3)
25
+ # this exception should kill this worker
26
+ raise "oups"
27
+ end
28
+ }
29
+ }
30
+ sleep 0.1
31
+ # 2 workers - 1 from raised exceptions = 1
32
+ assert_equal(@tpool.size, 1)
33
+ @tpool.shutdown
34
+ # same sum
35
+ assert_equal(sum_orig, sum_thr)
36
+ # number of different threads
37
+ assert_equal(uniq_threads.size, 2)
38
+ # after shutdown all threads are dead
39
+ assert_equal(@tpool.size, 0)
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{thread_tools}
3
+ s.version = '0.23'
4
+ s.summary = %q{Utilities for threaded apps}
5
+ s.platform = Gem::Platform::RUBY
6
+ s.email = %q{daniel@tralamazza.com}
7
+ s.homepage = %q{http://github.com/differential/thread_tools}
8
+ s.description = %q{Thread tools is a collection of classes and utilities to help you write threaded code.}
9
+ s.has_rdoc = true
10
+ s.authors = ['Daniel Tralamazza']
11
+ s.files = [
12
+ 'thread_tools.gemspec',
13
+ 'README.rdoc',
14
+ 'LICENSE',
15
+ 'lib/thread_tools/threadpool.rb',
16
+ 'lib/thread_tools/mongrel_pool.rb',
17
+ 'lib/thread_tools/semaphore.rb',
18
+ 'lib/thread_tools/debugmutex.rb',
19
+ 'test/test_mongrel.rb',
20
+ 'test/test_semaphore.rb',
21
+ 'test/test_threadpool.rb',
22
+ ]
23
+ s.test_files = [
24
+ 'test/test_mongrel.rb',
25
+ 'test/test_semaphore.rb',
26
+ 'test/test_threadpool.rb',
27
+ ]
28
+ s.rdoc_options = ['--main', 'README.rdoc']
29
+ s.extra_rdoc_files = ['README.rdoc']
30
+ s.require_paths = ['lib']
31
+ s.rubyforge_project = 'threadtools'
32
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thread_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.23"
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Tralamazza
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-05 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Thread tools is a collection of classes and utilities to help you write threaded code.
17
+ email: daniel@tralamazza.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - thread_tools.gemspec
26
+ - README.rdoc
27
+ - LICENSE
28
+ - lib/thread_tools/threadpool.rb
29
+ - lib/thread_tools/mongrel_pool.rb
30
+ - lib/thread_tools/semaphore.rb
31
+ - lib/thread_tools/debugmutex.rb
32
+ - test/test_mongrel.rb
33
+ - test/test_semaphore.rb
34
+ - test/test_threadpool.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/differential/thread_tools
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --main
42
+ - README.rdoc
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project: threadtools
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Utilities for threaded apps
64
+ test_files:
65
+ - test/test_mongrel.rb
66
+ - test/test_semaphore.rb
67
+ - test/test_threadpool.rb