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 +22 -0
- data/README.rdoc +57 -0
- data/lib/thread_tools/debugmutex.rb +56 -0
- data/lib/thread_tools/mongrel_pool.rb +76 -0
- data/lib/thread_tools/semaphore.rb +39 -0
- data/lib/thread_tools/threadpool.rb +101 -0
- data/test/test_mongrel.rb +45 -0
- data/test/test_semaphore.rb +25 -0
- data/test/test_threadpool.rb +41 -0
- data/thread_tools.gemspec +32 -0
- metadata +67 -0
    
        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
         |