clean_thread 1.0.0
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 +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/NEWS.rdoc +29 -0
- data/README.rdoc +72 -0
- data/Rakefile +7 -0
- data/clean_thread.gemspec +26 -0
- data/lib/clean_thread.rb +134 -0
- data/lib/clean_thread/version.rb +3 -0
- data/test/test_helper.rb +3 -0
- data/test/unit/cleanthread_test.rb +50 -0
- metadata +84 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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
|
data/lib/clean_thread.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
@@ -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
|