knjappserver 0.0.16 → 0.0.17
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/VERSION +1 -1
- data/bin/knjappserver_start.rb +17 -25
- data/knjappserver.gemspec +17 -6
- data/lib/conf/conf_example.rb +3 -3
- data/lib/files/database_schema.rb +1 -2
- data/lib/include/class_customio.rb +8 -25
- data/lib/include/class_erbhandler.rb +7 -1
- data/lib/include/class_httpserver.rb +50 -34
- data/lib/include/class_httpsession.rb +138 -101
- data/lib/include/class_httpsession_contentgroup.rb +52 -24
- data/lib/include/class_httpsession_http_request.rb +203 -0
- data/lib/include/{class_httpresp.rb → class_httpsession_http_response.rb} +27 -28
- data/lib/include/class_httpsession_post_multipart.rb +117 -0
- data/lib/include/class_knjappserver.rb +146 -96
- data/lib/include/class_knjappserver_cleaner.rb +74 -68
- data/lib/include/class_knjappserver_cmdline.rb +23 -17
- data/lib/include/class_knjappserver_errors.rb +121 -104
- data/lib/include/class_knjappserver_leakproxy_client.rb +6 -0
- data/lib/include/class_knjappserver_leakproxy_server.rb +56 -0
- data/lib/include/class_knjappserver_logging.rb +25 -25
- data/lib/include/class_knjappserver_mailing.rb +84 -56
- data/lib/include/class_knjappserver_sessions.rb +15 -22
- data/lib/include/class_knjappserver_threadding.rb +70 -43
- data/lib/include/class_knjappserver_threadding_timeout.rb +20 -4
- data/lib/include/class_knjappserver_translations.rb +6 -4
- data/lib/include/class_knjappserver_web.rb +87 -35
- data/lib/include/class_log.rb +9 -9
- data/lib/include/class_log_link.rb +4 -4
- data/lib/include/class_session.rb +8 -4
- data/lib/include/gettext_funcs.rb +8 -6
- data/lib/include/magic_methods.rb +4 -0
- data/lib/pages/debug_database_connections.rhtml +46 -0
- data/lib/pages/debug_http_sessions.rhtml +40 -0
- data/lib/pages/error_notfound.rhtml +12 -0
- data/lib/pages/spec.rhtml +1 -1
- data/lib/pages/spec_post.rhtml +3 -0
- data/lib/pages/spec_thread_joins.rhtml +21 -0
- data/lib/pages/spec_threadded_content.rhtml +2 -0
- data/lib/pages/tests.rhtml +14 -0
- data/lib/scripts/benchmark.rb +25 -8
- data/lib/scripts/knjappserver_cgi.rb +60 -0
- data/lib/scripts/knjappserver_fcgi.rb +135 -0
- data/lib/scripts/leakproxy.rb +27 -0
- data/spec/knjappserver_spec.rb +16 -5
- data/spec/leakproxy_spec.rb +56 -0
- metadata +38 -27
- data/lib/include/class_httpsession_knjengine.rb +0 -154
- data/lib/include/class_httpsession_mongrel.rb +0 -75
- data/lib/include/class_httpsession_webrick.rb +0 -75
@@ -0,0 +1,56 @@
|
|
1
|
+
#This class starts a Knjappserver in another process. This process can be used for scripts that leak memory. The memoy-usage is
|
2
|
+
#looked over and the process restarted when it reaches a certain point. Doing the restart all waiting requests will wait gracefully.
|
3
|
+
class Knjappserver::Leakproxy_server
|
4
|
+
def initialize(args)
|
5
|
+
require "#{$knjpath}/process"
|
6
|
+
|
7
|
+
leakproxy_path = "#{File.dirname(__FILE__)}/../scripts/leakproxy.rb"
|
8
|
+
executable = Knj::Os.executed_executable
|
9
|
+
|
10
|
+
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(executable, leakproxy_path, "r+")
|
11
|
+
@kas = args[:kas]
|
12
|
+
@config = @kas.config
|
13
|
+
|
14
|
+
Thread.new do
|
15
|
+
STDOUT.print "Doing loop:\n"
|
16
|
+
@stderr.each_line do |str|
|
17
|
+
STDOUT.print "Test: #{str}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
pass_conf = {}
|
22
|
+
pass_conf_keys = [:knjrbfw_path]
|
23
|
+
pass_conf_keys.each do |key, val|
|
24
|
+
pass_conf[key] = val if @config.key?(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
args_pass = {
|
28
|
+
:config => pass_conf
|
29
|
+
}
|
30
|
+
|
31
|
+
@stdin.write("#{Marshal.dump(args_pass)}\n")
|
32
|
+
|
33
|
+
@process = Knj::Process.new(
|
34
|
+
:out => @stdin,
|
35
|
+
:in => @stdout,
|
36
|
+
:err => @stderr,
|
37
|
+
:listen => true,
|
38
|
+
:debug => true,
|
39
|
+
:on_rec => proc{|d|
|
40
|
+
obj = d.obj
|
41
|
+
|
42
|
+
if obj.is_a?(Hash)
|
43
|
+
if obj["type"] == "print"
|
44
|
+
STDOUT.print obj["str"]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
STDOUT.print Knj::Php.print_r(obj, true)
|
48
|
+
end
|
49
|
+
}
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def spawn
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -259,17 +259,17 @@ class Knjappserver
|
|
259
259
|
links = @ob.list(:Log_link, {"object_class" => obj.class.name, "object_id" => obj.id, "limit" => 500, "orderby" => [["id", "desc"]]})
|
260
260
|
|
261
261
|
html = "<table class=\"list knjappserver_log_table\">"
|
262
|
-
html
|
263
|
-
html
|
264
|
-
html
|
265
|
-
html
|
266
|
-
html
|
267
|
-
html
|
268
|
-
html
|
269
|
-
html
|
270
|
-
html
|
271
|
-
html
|
272
|
-
html
|
262
|
+
html << "<thead>"
|
263
|
+
html << "<tr>"
|
264
|
+
html << "<th>ID</th>"
|
265
|
+
html << "<th>Message</th>"
|
266
|
+
html << "<th style=\"width: 130px;\">Date & time</th>"
|
267
|
+
html << "<th>Tag</th>"
|
268
|
+
html << "<th>Objects</th>" if args[:ob_use]
|
269
|
+
html << "<th>IP</th>" if args[:show_ip]
|
270
|
+
html << "</tr>"
|
271
|
+
html << "</thead>"
|
272
|
+
html << "<tbody>"
|
273
273
|
|
274
274
|
links.each do |link|
|
275
275
|
log = link.log
|
@@ -280,32 +280,32 @@ class Knjappserver
|
|
280
280
|
classes = ["knjappserver_log", "knjappserver_log_#{log.id}"]
|
281
281
|
classes << "knjappserver_log_multiple_lines" if msg_lines.length > 1
|
282
282
|
|
283
|
-
html
|
284
|
-
html
|
285
|
-
html
|
286
|
-
html
|
287
|
-
html
|
283
|
+
html << "<tr class=\"#{classes.join(" ")}\">"
|
284
|
+
html << "<td>#{log.id}</td>"
|
285
|
+
html << "<td>#{first_line.html}</td>"
|
286
|
+
html << "<td>#{log.date_saved_str}</td>"
|
287
|
+
html << "<td>#{log.tag.html}</td>"
|
288
288
|
|
289
289
|
if args[:ob_use]
|
290
290
|
begin
|
291
|
-
html
|
291
|
+
html << "<td>#{log.objects_html(args[:ob_use])}</td>"
|
292
292
|
rescue => e
|
293
|
-
html
|
293
|
+
html << "<td>#{e.message.html}</td>"
|
294
294
|
end
|
295
295
|
end
|
296
296
|
|
297
|
-
html
|
298
|
-
html
|
297
|
+
html << "<td>#{log.ip}</td>" if args[:show_ip]
|
298
|
+
html << "</tr>"
|
299
299
|
end
|
300
300
|
|
301
301
|
if links.empty?
|
302
|
-
html
|
303
|
-
html
|
304
|
-
html
|
302
|
+
html << "<tr>"
|
303
|
+
html << "<td colspan=\"2\" class=\"error\">No logs were found for that object.</td>"
|
304
|
+
html << "</tr>"
|
305
305
|
end
|
306
306
|
|
307
|
-
html
|
308
|
-
html
|
307
|
+
html << "</tbody>"
|
308
|
+
html << "</table>"
|
309
309
|
|
310
310
|
return html
|
311
311
|
end
|
@@ -2,8 +2,7 @@ class Knjappserver
|
|
2
2
|
attr_reader :mails_waiting
|
3
3
|
|
4
4
|
def initialize_mailing
|
5
|
-
|
6
|
-
require "mail" if !@config.has_key?(:mail_require) or @config[:mail_require]
|
5
|
+
require "knj/autoload/ping"
|
7
6
|
|
8
7
|
@mails_waiting = []
|
9
8
|
@mails_mutex = Mutex.new
|
@@ -15,6 +14,8 @@ class Knjappserver
|
|
15
14
|
|
16
15
|
#Queue a mail for sending. Possible keys are: :subject, :from, :to, :text and :html.
|
17
16
|
def mail(mail_args)
|
17
|
+
raise "'smtp_args' has not been given for the Knjappserver." if !@config[:smtp_args]
|
18
|
+
|
18
19
|
@mails_queue_mutex.synchronize do
|
19
20
|
count_wait = 0
|
20
21
|
while @mails_waiting.length > 100
|
@@ -36,28 +37,44 @@ class Knjappserver
|
|
36
37
|
#Sends all queued mails to the respective servers, if we are online.
|
37
38
|
def mail_flush
|
38
39
|
@mails_mutex.synchronize do
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
40
|
+
return false if @mails_waiting.length <= 0
|
41
|
+
|
42
|
+
status = Ping.pingecho("google.dk", 10, 80)
|
43
|
+
if !status
|
44
|
+
STDOUT.print "We are not online - skipping mail flush.\n"
|
45
|
+
return false #Dont run if we dont have a connection to the internet and then properly dont have a connection to the SMTP as well.
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
#Use subprocessing to avoid the mail-framework (activesupport and so on, also possible memory leaks in those large frameworks).
|
50
|
+
require "knj/process_meta"
|
51
|
+
subproc = Knj::Process_meta.new("debug" => @debug, "debug_err" => true)
|
52
|
+
subproc.static("Object", "require", "rubygems")
|
53
|
+
subproc.static("Object", "require", "mail")
|
54
|
+
subproc.static("Object", "require", "#{@config[:knjrbfw_path]}knjrbfw")
|
55
|
+
subproc.static("Object", "require", "knj/autoload")
|
56
|
+
|
57
|
+
STDOUT.print "Flushing emails." if @debug
|
58
|
+
@mails_waiting.each do |mail|
|
59
|
+
begin
|
60
|
+
STDOUT.print "Sending email: #{mail.__id__}\n" if @debug
|
61
|
+
if mail.send("proc" => subproc)
|
62
|
+
STDOUT.print "Email sent: #{mail.__id__}\n" if @debug
|
63
|
+
@mails_waiting.delete(mail)
|
64
|
+
end
|
65
|
+
rescue Timeout::Error
|
66
|
+
#ignore -
|
67
|
+
rescue => e
|
68
|
+
@mails_waiting.delete(mail)
|
69
|
+
self.handle_error(e, {:email => false})
|
70
|
+
end
|
71
|
+
|
72
|
+
sleep 1 #sleep so we dont take up too much bandwidth.
|
73
|
+
end
|
74
|
+
ensure
|
75
|
+
subproc.destroy if subproc
|
76
|
+
subproc = nil
|
77
|
+
end
|
61
78
|
end
|
62
79
|
end
|
63
80
|
|
@@ -77,43 +94,54 @@ class Knjappserver
|
|
77
94
|
end
|
78
95
|
|
79
96
|
#Sends the email to the receiver.
|
80
|
-
def send
|
97
|
+
def send(args = {})
|
81
98
|
STDOUT.print "Sending mail '#{__id__}'.\n" if @args[:kas].debug
|
82
99
|
|
83
|
-
|
100
|
+
if @args[:from]
|
101
|
+
from = @args[:from]
|
102
|
+
elsif @args[:kas].config[:error_report_from]
|
103
|
+
from = @args[:kas].config[:error_report_from]
|
104
|
+
else
|
105
|
+
raise "Dont know where to take the 'from'-paramter from - none given in appserver config or mail-method-arguments?"
|
106
|
+
end
|
107
|
+
|
108
|
+
if args["proc"]
|
109
|
+
args["proc"].static("Object", "require", "knj/mailobj")
|
110
|
+
mail = args["proc"].new("Knj::Mailobj", @args[:kas].config[:smtp_args])
|
111
|
+
mail._pm_send_noret("to=", @args[:to])
|
112
|
+
mail._pm_send_noret("subject=", @args[:subject]) if @args[:subject]
|
113
|
+
mail._pm_send_noret("html=", Knj::Strings.email_str_safe(@args[:html])) if @args[:html]
|
114
|
+
mail._pm_send_noret("text=", Knj::Strings.email_str_safe(@args[:text])) if @args[:text]
|
115
|
+
mail._pm_send_noret("from=", from)
|
116
|
+
mail._pm_send_noret("send")
|
117
|
+
else
|
118
|
+
require "knj/mailobj"
|
84
119
|
mail = Knj::Mailobj.new(@args[:kas].config[:smtp_args])
|
85
120
|
mail.to = @args[:to]
|
86
121
|
mail.subject = @args[:subject] if @args[:subject]
|
87
|
-
mail.html = @args[:html] if @args[:html]
|
88
|
-
mail.text = @args[:text] if @args[:text]
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
@args[:errors][e.class.name][:count] += 1
|
111
|
-
raise e if @args[:errors][e.class.name][:count] >= 5
|
112
|
-
@args[:status] = :error
|
113
|
-
@args[:error] = e
|
114
|
-
|
115
|
-
return false
|
116
|
-
end
|
122
|
+
mail.html = Knj::Strings.email_str_safe(@args[:html]) if @args[:html]
|
123
|
+
mail.text = Knj::Strings.email_str_safe(@args[:text]) if @args[:text]
|
124
|
+
mail.from = from
|
125
|
+
mail.send
|
126
|
+
end
|
127
|
+
|
128
|
+
@args[:status] = :sent
|
129
|
+
STDOUT.print "Sent email #{self.__id__}\n" if @args[:kas].debug
|
130
|
+
return true
|
131
|
+
rescue Exception => e
|
132
|
+
if @args[:kas].debug
|
133
|
+
STDOUT.print "Could not send email.\n"
|
134
|
+
STDOUT.puts e.inspect
|
135
|
+
STDOUT.puts e.backtrace
|
136
|
+
end
|
137
|
+
|
138
|
+
@args[:errors][e.class.name] = {:count => 0} if !@args[:errors].has_key?(e.class.name)
|
139
|
+
@args[:errors][e.class.name][:count] += 1
|
140
|
+
raise e if @args[:errors][e.class.name][:count] >= 5
|
141
|
+
@args[:status] = :error
|
142
|
+
@args[:error] = e
|
143
|
+
|
144
|
+
return false
|
117
145
|
end
|
118
146
|
end
|
119
147
|
end
|
@@ -4,9 +4,7 @@ class Knjappserver
|
|
4
4
|
end
|
5
5
|
|
6
6
|
#Returns or adds session based on idhash and meta-data.
|
7
|
-
def session_fromid(
|
8
|
-
ip = args[:ip].to_s
|
9
|
-
idhash = args[:idhash].to_s
|
7
|
+
def session_fromid(ip, idhash, meta)
|
10
8
|
ip = "bot" if idhash == "bot"
|
11
9
|
|
12
10
|
if !@sessions.key?(idhash)
|
@@ -14,42 +12,31 @@ class Knjappserver
|
|
14
12
|
if !session
|
15
13
|
session = @ob.add(:Session, {
|
16
14
|
:idhash => idhash,
|
17
|
-
:user_agent =>
|
15
|
+
:user_agent => meta["HTTP_USER_AGENT"],
|
18
16
|
:ip => ip
|
19
17
|
})
|
20
18
|
end
|
21
19
|
|
20
|
+
hash = {}
|
22
21
|
@sessions[idhash] = {
|
23
22
|
:dbobj => session,
|
24
|
-
:hash =>
|
23
|
+
:hash => hash
|
25
24
|
}
|
26
25
|
else
|
27
26
|
session = @sessions[idhash][:dbobj]
|
27
|
+
hash = @sessions[idhash][:hash]
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
if session[:user_agent] != args[:meta]["HTTP_USER_AGENT"]
|
33
|
-
STDOUT.print "Invalid user-agent!\n"
|
34
|
-
STDOUT.print Knj::Php.print_r(session, true)
|
35
|
-
|
36
|
-
raise Knj::Errors::InvalidData, "Invalid user-agent."
|
37
|
-
elsif !session.remember? and ip.to_s != session[:ip].to_s
|
38
|
-
STDOUT.print "Invalid IP!\n"
|
39
|
-
STDOUT.print Knj::Php.print_r(session, true)
|
40
|
-
|
41
|
-
raise Knj::Errors::InvalidData, "Invalid IP."
|
42
|
-
end
|
30
|
+
if ip != "bot" and !session.remember? and ip.to_s != session[:ip].to_s
|
31
|
+
raise Knj::Errors::InvalidData, "Invalid IP."
|
43
32
|
end
|
44
|
-
=end
|
45
33
|
|
46
34
|
@sessions[idhash][:time_lastused] = Time.now
|
47
|
-
return
|
35
|
+
return [session, hash]
|
48
36
|
end
|
49
37
|
|
50
38
|
#Generates a new session-ID by the meta data.
|
51
|
-
def session_generate_id(
|
52
|
-
meta = args[:meta]
|
39
|
+
def session_generate_id(meta)
|
53
40
|
return Digest::MD5.hexdigest("#{Time.now.to_f}_#{meta["HTTP_HOST"]}_#{meta["REMOTE_HOST"]}_#{meta["HTTP_X_FORWARDED_SERVER"]}_#{meta["HTTP_X_FORWARDED_FOR"]}_#{meta["HTTP_X_FORWARDED_HOST"]}_#{meta["REMOTE_ADDR"]}_#{meta["HTTP_USER_AGENT"]}")
|
54
41
|
end
|
55
42
|
|
@@ -75,4 +62,10 @@ class Knjappserver
|
|
75
62
|
end
|
76
63
|
end
|
77
64
|
end
|
65
|
+
|
66
|
+
#Writes all session-data and resets the hash.
|
67
|
+
def sessions_reset
|
68
|
+
self.sessions_flush
|
69
|
+
@sessions = {}
|
70
|
+
end
|
78
71
|
end
|
@@ -1,27 +1,33 @@
|
|
1
1
|
class Knjappserver
|
2
|
-
|
2
|
+
def initialize_threadding
|
3
3
|
@config[:threadding] = {} if !@config.has_key?(:threadding)
|
4
|
-
@config[:threadding][:max_running] =
|
4
|
+
@config[:threadding][:max_running] = 1 if !@config[:threadding].has_key?(:max_running)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
@threadpool = Knj::Threadpool.new(:threads => @config[:threadding][:max_running], :sleep => 0.1)
|
7
|
+
@threadpool.events.connect(:on_error) do |event, error|
|
8
|
+
self.handle_error(error)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#Inits the thread so it has access to the appserver and various magic methods can be used.
|
13
|
+
def thread_init(thread = nil)
|
14
|
+
thread = Thread.current if thread == nil
|
15
15
|
thread[:knjappserver] = {} if !thread[:knjappserver]
|
16
16
|
thread[:knjappserver][:kas] = self
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
end
|
18
|
+
|
19
|
+
#Spawns a new thread with access to magic methods, _db-method and various other stuff in the appserver.
|
20
|
+
def thread(args = {})
|
21
|
+
raise "No block given." if !block_given?
|
22
|
+
args[:args] = [] if !args[:args]
|
23
|
+
|
24
|
+
thread_obj = Knjappserver::Thread_instance.new(
|
25
|
+
:running => false,
|
26
|
+
:error => false,
|
27
|
+
:done => false
|
28
|
+
)
|
29
|
+
|
30
|
+
@threadpool.run_async do
|
25
31
|
@ob.db.get_and_register_thread if @ob.db.opts[:threadsafe]
|
26
32
|
@db_handler.get_and_register_thread if @db_handler.opts[:threadsafe]
|
27
33
|
|
@@ -30,31 +36,52 @@ class Knjappserver
|
|
30
36
|
:db => @db_handler
|
31
37
|
}
|
32
38
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
begin
|
40
|
+
thread_obj.args[:running] = true
|
41
|
+
yield(*args[:args])
|
42
|
+
rescue Exception => e
|
43
|
+
handle_error(e)
|
44
|
+
thread_obj.args[:error] = true
|
45
|
+
thread_obj.args[:error_obj] = e
|
46
|
+
ensure
|
47
|
+
@ob.db.free_thread if @ob.db.opts[:threadsafe]
|
48
|
+
@db_handler.free_thread if @db_handler.opts[:threadsafe]
|
49
|
+
thread_obj.args[:running] = false
|
50
|
+
thread_obj.args[:done] = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return thread_obj
|
55
|
+
end
|
56
|
+
|
57
|
+
#Runs a proc every number of seconds.
|
58
|
+
def timeout(args = {}, &block)
|
59
|
+
return Knjappserver::Threadding_timeout.new({
|
47
60
|
:kas => self,
|
48
61
|
:block => block,
|
49
|
-
:args =>
|
50
|
-
)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
#Spawns a thread to run the given proc and add the output of that block in the correct order to the HTML.
|
57
|
-
def threadded_content(&block)
|
62
|
+
:args => []
|
63
|
+
}.merge(args)).start
|
64
|
+
end
|
65
|
+
|
66
|
+
#Spawns a thread to run the given proc and add the output of that block in the correct order to the HTML.
|
67
|
+
def threadded_content(&block)
|
58
68
|
_httpsession.threadded_content(block)
|
59
|
-
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Knjappserver::Thread_instance
|
73
|
+
attr_reader :args
|
74
|
+
|
75
|
+
def initialize(args)
|
76
|
+
@args = args
|
77
|
+
end
|
78
|
+
|
79
|
+
def join
|
80
|
+
sleep 0.1 while !@args[:done]
|
81
|
+
end
|
82
|
+
|
83
|
+
def join_error
|
84
|
+
self.join
|
85
|
+
raise @args[:error_obj] if @args[:error_obj]
|
86
|
+
end
|
60
87
|
end
|