clean_thread 1.0.0

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 clean_thread.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Infonium Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NEWS.rdoc ADDED
@@ -0,0 +1,29 @@
1
+ = Release Notes
2
+
3
+ == 1.0.0 - 2011-06-28 - Dwayne Litzenberger
4
+
5
+ - First public release
6
+
7
+ == 0.5 - 2011-04-04 - Dwayne Litzenberger
8
+
9
+ - Set the Java thread name (when running under JRuby). This makes debugging
10
+ with jconsole/JMX easier.
11
+
12
+ == 0.4 - 2011-02-26 - Dwayne Litzenberger
13
+
14
+ - Fix bug where ThreadFinish was raised when a thread invoked its own
15
+ CleanThread#finish method with :nowait=>true.
16
+
17
+ == 0.3 - 2011-02-24 - Dwayne Litzenberger
18
+
19
+ - Fix bug where CleanThread#alive? would raise an exception if the thread was
20
+ not yet started.
21
+
22
+ == 0.2 - 2010-10-21 - Dwayne Litzenberger
23
+
24
+ - Removed references to HospitalPortal::Database, use ActiveRecord::Base instead.
25
+ - Add display_exception() method, which outputs exceptions to $stderr by default.
26
+
27
+ == 0.1 - 2010-02-10 - John Duff
28
+
29
+ - Removed dependency on HospitalPortal::Database and Java
data/README.rdoc ADDED
@@ -0,0 +1,72 @@
1
+ = clean_thread - Support for background threads.
2
+
3
+ CleanThread helps you create background threads that will exit cleanly upon
4
+ request.
5
+
6
+ You may either subclass this class and override its main() method, or pass
7
+ a block to CleanThread.new.
8
+
9
+ Code invoked by CleanThread should check periodically whether the
10
+ thread needs to exit, either by invoking the check_finishing method (which
11
+ raises ThreadFinish if the finish method has been called), or by manually
12
+ checking the result of the finishing? method and exiting if it returns true.
13
+
14
+ In addition to providing the #finish and #check_finishing methods,
15
+ CleanThread takes care of the following:
16
+
17
+ - Dumping a backtrace to stderr if there is an exception
18
+ - Setting the Java thread name (if running under JRuby)
19
+ - Releasing ActiveRecord connections (if ActiveRecord is loaded)
20
+
21
+ = Installation
22
+
23
+ gem install clean_thread
24
+
25
+ = Example Usage
26
+
27
+ == Overriding CleanThread#main
28
+
29
+ require 'clean_thread'
30
+
31
+ class MyThread < CleanThread
32
+ def main
33
+ loop do
34
+ check_finishing
35
+ # ... do some steps
36
+ check_finishing
37
+ # ... do some more steps
38
+ check_finishing
39
+ # ... do yet more steps
40
+ end
41
+ end
42
+ end
43
+
44
+ t = MyThread.new
45
+ t.start
46
+ # ...
47
+ t.finish
48
+
49
+ == Passing a block to new
50
+
51
+ require 'clean_thread'
52
+
53
+ t = CleanThread.new do
54
+ loop do
55
+ CleanThread.check_finishing
56
+ # ... do some steps
57
+ CleanThread.check_finishing
58
+ # ... do some more steps
59
+ CleanThread.check_finishing
60
+ # ... do yet more steps
61
+ end
62
+ end
63
+
64
+ t.start # Start the thread
65
+ # ...
66
+ t.finish # Stop the thread
67
+
68
+ = License
69
+
70
+ Copyright © 2009-2011 Infonium Inc.
71
+
72
+ License: See the MIT-LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "clean_thread/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "clean_thread"
7
+ s.version = CleanThread::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dwayne Litzenberger"]
10
+ s.email = ["dlitz@patientway.com"]
11
+ s.homepage = "https://github.com/dlitz/clean_thread"
12
+ s.summary = %q{Support for threads that exit cleanly}
13
+ s.description = <<EOF
14
+ HospitalPortal::CleanThread provides support for developing threads that exit cleanly.
15
+
16
+ Reliable J2EE deployment requires that all threads started by an application
17
+ are able to exit cleanly upon request.
18
+ EOF
19
+
20
+ s.rubyforge_project = "clean_thread"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,134 @@
1
+ require 'thread'
2
+
3
+ class CleanThread
4
+
5
+ class ThreadFinish < Exception
6
+ # NB: Most exceptions should inherit from StandardError, but this is deliberate.
7
+ end
8
+
9
+ # If the current thread was invoked by CleanThread, invoke
10
+ # CleanThread#check_finishing.
11
+ def self.check_finishing
12
+ ct = Thread.current[:clean_thread]
13
+ return nil if ct.nil?
14
+ ct.check_finishing
15
+ end
16
+
17
+ # If the current thread was invoked by CleanThread, return the result of
18
+ # CleanThread#finishing?. Otherwise, return nil.
19
+ def self.finishing?
20
+ ct = Thread.current[:clean_thread]
21
+ return nil if ct.nil?
22
+ return ct.finishing?
23
+ end
24
+
25
+ # Initialize a new CleanThread object.
26
+ #
27
+ # If a block is given, that block will be executed in a new thread when the
28
+ # start method is called. If no block is given, the main method will be used.
29
+ #
30
+ # The block is passed the CleanThread instance it belongs to, plus any
31
+ # arguments passed to the new method.
32
+ def initialize(*args, &block)
33
+ @_cleanthread_mutex = Mutex.new
34
+ @_cleanthread_stopping = false # locked by _cleanthread_mutex
35
+ @_cleanthread_thread = nil # locked by _cleanthread_mutex. Once set, it is not changed.
36
+ @_cleanthread_proc = block
37
+ @_cleanthread_args = args
38
+ end
39
+
40
+ # Start the thread.
41
+ def start
42
+ @_cleanthread_mutex.synchronize {
43
+ if @_cleanthread_thread.nil?
44
+ @_cleanthread_thread = Thread.new do
45
+ begin
46
+ # Set the Java thread name (for debugging)
47
+ Java::java.lang.Thread.current_thread.name = "#{self.class.name}-#{self.object_id}" if defined?(Java)
48
+
49
+ # Set the CleanThread instance (for use with the CleanThread.check_finishing
50
+ # and CleanThread.finishing? class methods
51
+ Thread.current[:clean_thread] = self
52
+
53
+ if @_cleanthread_proc.nil?
54
+ main(*@_cleanthread_args)
55
+ else
56
+ @_cleanthread_proc.call(self, *@_cleanthread_args)
57
+ end
58
+ rescue ThreadFinish
59
+ # Do nothing - exit cleanly
60
+ rescue Exception, ScriptError, SystemStackError, SyntaxError, StandardError => exc
61
+ # NOTE: rescue Exception should be enough here, but JRuby seems to miss some exceptions if you do that.
62
+ #
63
+ # Output backtrace, since otherwise we won't see anything until the main thread joins this thread.
64
+ display_exception(exc)
65
+ raise
66
+ ensure
67
+ # This is needed. Otherwise, the database connections aren't returned to the pool and things break.
68
+ ActiveRecord::Base.connection_handler.clear_active_connections! if defined? ActiveRecord::Base
69
+ end
70
+ end
71
+ else
72
+ raise TypeError.new("Thread already started")
73
+ end
74
+ }
75
+ return nil
76
+ end
77
+
78
+ # Ask the thread to finish, and wait for the thread to stop.
79
+ #
80
+ # If the :nowait option is true, then just ask the thread to finish without
81
+ # waiting for it to stop.
82
+ #
83
+ # When a thread invokes its own finish method, ThreadFinish is raised,
84
+ # unless :nowait is true.
85
+ def finish(options={})
86
+ @_cleanthread_mutex.synchronize {
87
+ raise RuntimeError.new("not started") if @_cleanthread_thread.nil?
88
+ @_cleanthread_stopping = true
89
+ }
90
+ unless options[:nowait]
91
+ raise ThreadFinish if @_cleanthread_thread == ::Thread.current
92
+ @_cleanthread_thread.join
93
+ end
94
+ return nil
95
+ end
96
+
97
+ # Return true if the thread is alive.
98
+ def alive?
99
+ @_cleanthread_thread && @_cleanthread_thread.alive?
100
+ end
101
+
102
+ # Wait for the thread to stop.
103
+ def join
104
+ return @_cleanthread_thread.join
105
+ end
106
+
107
+ # Return true if the finish method has been called.
108
+ def finishing?
109
+ @_cleanthread_mutex.synchronize { return @_cleanthread_stopping }
110
+ end
111
+
112
+ # Exit the thread if the finish method has been called.
113
+ #
114
+ # Functionally equivalent to:
115
+ # raise ThreadFinish if finishing?
116
+ def check_finishing
117
+ raise ThreadFinish if finishing?
118
+ return nil
119
+ end
120
+
121
+ protected
122
+ # Override this method if you want this thread to do something.
123
+ def main(cleanthread)
124
+ # default is to do nothing
125
+ end
126
+
127
+ # Override this method if you want the stacktrace output to happen differently
128
+ def display_exception(exception)
129
+ lines = []
130
+ lines << "#{exception.class.name}: #{exception.message}\n"
131
+ lines += exception.backtrace.map{|line| "\tfrom #{line}"}
132
+ $stderr.puts lines.join("\n")
133
+ end
134
+ end
@@ -0,0 +1,3 @@
1
+ class CleanThread # reopen
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../lib")
@@ -0,0 +1,50 @@
1
+ require File.expand_path "../../test_helper", __FILE__
2
+
3
+ require 'clean_thread'
4
+
5
+ class CleanThreadTest < Test::Unit::TestCase
6
+ def test_allow_multple_finish
7
+ t = CleanThread.new do |t|
8
+ loop do
9
+ t.check_finishing
10
+ end
11
+ end
12
+ t.start
13
+ t.finish
14
+ assert_nothing_raised do
15
+ t.finish
16
+ end
17
+ end
18
+
19
+ def test_allow_multple_finish_with_first_nowait
20
+ t = CleanThread.new do |t|
21
+ loop do
22
+ t.check_finishing
23
+ end
24
+ end
25
+ t.start
26
+ t.finish(:nowait=>true)
27
+ sleep 0.1 while t.alive?
28
+ assert_nothing_raised do
29
+ t.finish
30
+ end
31
+ end
32
+
33
+ def test_alive_should_not_raise_error_if_thread_not_yet_started
34
+ t = CleanThread.new { }
35
+ assert_nothing_raised do
36
+ assert !t.alive?
37
+ end
38
+ end
39
+
40
+ def test_finish_nowait_should_not_raise_exception
41
+ success = false
42
+ t = CleanThread.new do |t|
43
+ t.finish(:nowait=>true)
44
+ success = true
45
+ end
46
+ t.start
47
+ t.join
48
+ assert success, "finish(:nowait=>true) should not raise an exception when invoked from within a CleanThread"
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clean_thread
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Dwayne Litzenberger
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-28 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: |
23
+ HospitalPortal::CleanThread provides support for developing threads that exit cleanly.
24
+
25
+ Reliable J2EE deployment requires that all threads started by an application
26
+ are able to exit cleanly upon request.
27
+
28
+ email:
29
+ - dlitz@patientway.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - MIT-LICENSE
40
+ - NEWS.rdoc
41
+ - README.rdoc
42
+ - Rakefile
43
+ - clean_thread.gemspec
44
+ - lib/clean_thread.rb
45
+ - lib/clean_thread/version.rb
46
+ - test/test_helper.rb
47
+ - test/unit/cleanthread_test.rb
48
+ has_rdoc: true
49
+ homepage: https://github.com/dlitz/clean_thread
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project: clean_thread
78
+ rubygems_version: 1.6.2
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Support for threads that exit cleanly
82
+ test_files:
83
+ - test/test_helper.rb
84
+ - test/unit/cleanthread_test.rb