From 27859754890e5562b33ffbb4b89e9f3c9c68df97 Mon Sep 17 00:00:00 2001 From: nagai Date: Fri, 17 Sep 2004 06:05:33 +0000 Subject: * ext/tcltklib/tcltklib.c: fix SEGV when (thread_)vwait or (thread_)tkwait * ext/tk/lib/tk.rb: add alias wait_window to wait_destroy * ext/tk/lib/multi-tk.rb: support calling 'mainloop' on slave interpreters (however, the 'real' eventloop must be run on the Default Master IP) * ext/tk/lib/remote-tk.rb: follow the changes of ext/tk/lib/multi-tk.rb * ext/tk/sample/remote-ip_sample2.rb: ditto * ext/tk/sample/tkoptdb-safeTk.rb: ditto git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@6916 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/tk/lib/multi-tk.rb | 441 +++++++++++++++++++++++++------------ ext/tk/lib/remote-tk.rb | 30 +-- ext/tk/lib/tk.rb | 9 + ext/tk/sample/remote-ip_sample2.rb | 7 +- ext/tk/sample/tkoptdb-safeTk.rb | 14 +- 5 files changed, 340 insertions(+), 161 deletions(-) (limited to 'ext/tk') diff --git a/ext/tk/lib/multi-tk.rb b/ext/tk/lib/multi-tk.rb index 2d758fc28c..cd4c916a2e 100644 --- a/ext/tk/lib/multi-tk.rb +++ b/ext/tk/lib/multi-tk.rb @@ -106,9 +106,18 @@ class MultiTkIp def _check_and_return(thread, exception, wait=0) unless thread unless exception.kind_of?(MultiTkIp_OK) || safe? + msg = "#{exception.class}: #{exception.message}" begin - @interp._eval(@interp._merge_tklist('bgerror', "#{exception.class}: #{exception.message}")) - rescue Exception + if @interp.deleted? + warn('Warning: ' + msg) + elsif @interp._eval_without_enc('info command bgerror').size != 0 + @interp._eval(@interp._merge_tklist('bgerror', msg)) + else + warn('Warning: ' + msg) + end + rescue Exception => e + warn('Warning: ' + msg) + warn('Warning: ' + e.message) end end return nil @@ -162,6 +171,17 @@ class MultiTkIp __getip.safe_level end + def wait_on_mainloop? + @wait_on_mainloop[0] + end + def wait_on_mainloop=(bool) + @wait_on_mainloop[0] = bool + end + + def running_mainloop? + @wait_on_mainloop[1] + end + def _destroy_slaves_of_slaveIP(ip) unless ip.deleted? ip._split_tklist(ip._invoke('interp', 'slaves')).each{|name| @@ -200,170 +220,211 @@ class MultiTkIp end end - def _create_receiver_and_watchdog(lvl = $SAFE) - lvl = $SAFE if lvl < $SAFE - - # command-procedures receiver - receiver = Thread.new(lvl){|safe_level| - loop do - break if @interp.deleted? - thread, cmd, *args = @cmd_queue.deq - if thread == @system - # control command - case cmd - when 'set_safe_level' - begin - safe_level = args[0] if safe_level < args[0] - rescue Exception - end - else - # ignore - end - - else - # procedure + def _receiver_eval_proc_core(safe_level, thread, cmd, *args) + begin + #ret = proc{$SAFE = safe_level; cmd.call(*args)}.call + ret = cmd.call(safe_level, *args) + + rescue SystemExit => e + # delete IP + unless @interp.deleted? + @slave_ip_tbl.each{|name, subip| + _destroy_slaves_of_slaveIP(subip) begin - #ret = proc{$SAFE = safe_level; cmd.call(*args)}.call - ret = cmd.call(safe_level, *args) - rescue SystemExit => e - # delete IP - unless @interp.deleted? - @slave_ip_tbl.each{|name, subip| - _destroy_slaves_of_slaveIP(subip) - begin - subip._eval_without_enc("foreach i [after info] {after cancel $i}") - rescue Exception - end + subip._eval_without_enc("foreach i [after info] {after cancel $i}") + rescue Exception + end =begin - begin - subip._invoke('destroy', '.') unless subip.deleted? - rescue Exception - end + begin + subip._invoke('destroy', '.') unless subip.deleted? + rescue Exception + end =end - begin - # safe_base? - @interp._eval_without_enc("::safe::interpConfigure #{name}") - @interp._eval_without_enc("::safe::interpDelete #{name}") - rescue Exception - if subip.respond_to?(:safe_base?) && subip.safe_base? && - !subip.deleted? - # do 'exit' to call the delete_hook procedure - begin - subip._eval_without_enc('exit') - rescue Exception - end - else - begin - subip.delete unless subip.deleted? - rescue Exception - end - end - end - } - + begin + # safe_base? + @interp._eval_without_enc("::safe::interpConfigure #{name}") + @interp._eval_without_enc("::safe::interpDelete #{name}") + rescue Exception + if subip.respond_to?(:safe_base?) && subip.safe_base? && + !subip.deleted? + # do 'exit' to call the delete_hook procedure begin - @interp._eval_without_enc("foreach i [after info] {after cancel $i}") + subip._eval_without_enc('exit') rescue Exception end + else begin - @interp._invoke('destroy', '.') unless @interp.deleted? + subip.delete unless subip.deleted? rescue Exception end - if @safe_base && !@interp.deleted? - # do 'exit' to call the delete_hook procedure - @interp._eval_without_enc('exit') - else - @interp.delete unless @interp.deleted? - end end + end + } - if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/ - _check_and_return(thread, MultiTkIp_OK.new($3 == 'exit')) - else - _check_and_return(thread, MultiTkIp_OK.new(nil)) - end + begin + @interp._eval_without_enc("foreach i [after info] {after cancel $i}") + rescue Exception + end + begin + @interp._invoke('destroy', '.') unless @interp.deleted? + rescue Exception + end + if @safe_base && !@interp.deleted? + # do 'exit' to call the delete_hook procedure + @interp._eval_without_enc('exit') + else + @interp.delete unless @interp.deleted? + end + end + + if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/ + _check_and_return(thread, MultiTkIp_OK.new($3 == 'exit')) + else + _check_and_return(thread, MultiTkIp_OK.new(nil)) + end - if master? && !safe? && allow_ruby_exit? + if master? && !safe? && allow_ruby_exit? =begin - ObjectSpace.each_object(TclTkIp){|obj| - obj.delete unless obj.deleted? - } + ObjectSpace.each_object(TclTkIp){|obj| + obj.delete unless obj.deleted? + } =end - exit + exit + end + # break + + rescue SecurityError => e + # in 'exit', 'exit!', and 'abort' : security error --> delete IP + if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/ + ret = ($3 == 'exit') + unless @interp.deleted? + @slave_ip_tbl.each_value{|subip| + _destroy_slaves_of_slaveIP(subip) + begin + subip._eval_without_enc("foreach i [after info] {after cancel $i}") + rescue Exception end - break - - rescue SecurityError => e - # in 'exit', 'exit!', and 'abort' : security error --> delete IP - if e.backtrace[0] =~ /^(.+?):(\d+):in `(exit|exit!|abort)'/ - ret = ($3 == 'exit') - unless @interp.deleted? - @slave_ip_tbl.each_value{|subip| - _destroy_slaves_of_slaveIP(subip) - begin - subip._eval_without_enc("foreach i [after info] {after cancel $i}") - rescue Exception - end =begin - begin - subip._invoke('destroy', '.') unless subip.deleted? - rescue Exception - end + begin + subip._invoke('destroy', '.') unless subip.deleted? + rescue Exception + end =end - begin - # safe_base? - @interp._eval_without_enc("::safe::interpConfigure #{name}") - @interp._eval_without_enc("::safe::interpDelete #{name}") - rescue Exception - if subip.respond_to?(:safe_base?) && subip.safe_base? && - !subip.deleted? - # do 'exit' to call the delete_hook procedure - begin - subip._eval_without_enc('exit') - rescue Exception - end - else - begin - subip.delete unless subip.deleted? - rescue Exception - end - end - end - } - + begin + # safe_base? + @interp._eval_without_enc("::safe::interpConfigure #{name}") + @interp._eval_without_enc("::safe::interpDelete #{name}") + rescue Exception + if subip.respond_to?(:safe_base?) && subip.safe_base? && + !subip.deleted? + # do 'exit' to call the delete_hook procedure begin - @interp._eval_without_enc("foreach i [after info] {after cancel $i}") + subip._eval_without_enc('exit') rescue Exception end -=begin + else begin - @interp._invoke('destroy', '.') unless @interp.deleted? + subip.delete unless subip.deleted? rescue Exception end -=end - if @safe_base && !@interp.deleted? - # do 'exit' to call the delete_hook procedure - @interp._eval_without_enc('exit') - else - @interp.delete unless @interp.deleted? - end end - _check_and_return(thread, MultiTkIp_OK.new(ret)) - break - - else - # raise security error - _check_and_return(thread, e) end + } - rescue Exception => e - # raise exception - _check_and_return(thread, e) + begin + @interp._eval_without_enc("foreach i [after info] {after cancel $i}") + rescue Exception + end +=begin + begin + @interp._invoke('destroy', '.') unless @interp.deleted? + rescue Exception + end +=end + if @safe_base && !@interp.deleted? + # do 'exit' to call the delete_hook procedure + @interp._eval_without_enc('exit') + else + @interp.delete unless @interp.deleted? + end + end + _check_and_return(thread, MultiTkIp_OK.new(ret)) + # break + + else + # raise security error + _check_and_return(thread, e) + end + + rescue Exception => e + # raise exception + _check_and_return(thread, e) + + else + # no exception + _check_and_return(thread, MultiTkIp_OK.new(ret)) + end + end + + def _receiver_eval_proc(last_thread, safe_level, thread, cmd, *args) + if thread + Thread.new{ + last_thread.join if last_thread + unless @interp.deleted? + _receiver_eval_proc_core(safe_level, thread, cmd, *args) + end + } + else + Thread.new{ + unless @interp.deleted? + _receiver_eval_proc_core(safe_level, thread, cmd, *args) + end + } + last_thread + end + end + + private :_receiver_eval_proc, :_receiver_eval_proc_core + + def _receiver_mainloop(check_root) + Thread.new{ + while !@interp.deleted? + break if @interp._invoke_without_enc('info', 'command', '.').size == 0 + sleep 0.5 + end + } + end + + def _create_receiver_and_watchdog(lvl = $SAFE) + lvl = $SAFE if lvl < $SAFE + + # command-procedures receiver + receiver = Thread.new(lvl){|safe_level| + last_thread = nil + loop do + break if @interp.deleted? + thread, cmd, *args = @cmd_queue.deq + if thread == @system + # control command + case cmd + when 'set_safe_level' + begin + safe_level = args[0] if safe_level < args[0] + rescue Exception + end + when 'call_mainloop' + thread = args.shift + _check_and_return(thread, + MultiTkIp_OK.new(_receiver_mainloop(*args))) else - # no exception - _check_and_return(thread, MultiTkIp_OK.new(ret)) + # ignore end + + else + # procedure + last_thread = _receiver_eval_proc(last_thread, safe_level, + thread, cmd, *args) end end } @@ -431,6 +492,8 @@ class MultiTkIp @system = Object.new + @wait_on_mainloop = [true, false] + @threadgroup = Thread.current.group @safe_base = false @@ -848,6 +911,8 @@ class MultiTkIp @system = Object.new + @wait_on_mainloop = [true, false] + @threadgroup = ThreadGroup.new @cmd_queue = Queue.new @@ -1231,7 +1296,17 @@ class MultiTkIp # send cmd to the proc-queue unless req_val - @cmd_queue.enq([nil, cmd, *args]) + begin + @cmd_queue.enq([nil, cmd, *args]) + rescue Exception => e + # ignore + if $DEBUG || true + warn("Warning: " + e.class.inspect + + ((e.message.length > 0)? ' "' + e.message + '"': '') + + " on " + self.inspect) + end + return e + end return nil end @@ -1289,6 +1364,9 @@ class MultiTkIp end =end def eval_proc(*args) + # The scope of the eval-block of 'eval_proc' method is different from + # the enternal. If you want to pass local values to the eval-block, + # use arguments of eval_proc method. They are passed to block-arguments. if block_given? cmd = Proc.new else @@ -1304,6 +1382,26 @@ class MultiTkIp end alias call eval_proc + def bg_eval_proc(*args) + if block_given? + cmd = Proc.new + else + unless (cmd = args.shift) + fail ArgumentError, "A Proc or Method object is expected for 1st argument" + end + end + Thread.new{ + eval_proc_core(false, + proc{|safe, *params| + $SAFE=safe; Thread.new(*params, &cmd).value + }, + *args) + } + end + alias background_eval_proc bg_eval_proc + alias bg_call bg_eval_proc + alias background_call bg_eval_proc + def eval_string(cmd, *eval_args) # cmd string ==> proc unless cmd.kind_of?(String) @@ -1313,6 +1411,20 @@ class MultiTkIp eval_proc_core(true, proc{|safe| $SAFE=safe; Kernel.eval(cmd, *eval_args)}) end alias eval_str eval_string + + def bg_eval_string(*args) + # cmd string ==> proc + unless cmd.kind_of?(String) + raise RuntimeError, "A String object is expected for the 'cmd' argument" + end + Thread.new{ + eval_proc_core(true, + proc{|safe| $SAFE=safe; Kernel.eval(cmd, *eval_args)}) + } + end + alias background_eval_string bg_eval_string + alias bg_eval_str bg_eval_string + alias background_eval_str bg_eval_string end class << MultiTkIp @@ -1582,11 +1694,50 @@ end # depend on TclTkIp class MultiTkIp def mainloop(check_root = true, restart_on_dead = false) - return self if self.slave? + #return self if self.slave? + #return self if self != @@DEFAULT_MASTER + if self != @@DEFAULT_MASTER + if @wait_on_mainloop[0] + begin + @wait_on_mainloop[1] = true + @cmd_queue.enq([@system, 'call_mainloop', + Thread.current, check_root]) + Thread.stop + rescue MultiTkIp_OK => ret + # return value + @wait_on_mainloop[1] = false + return ret.value.value + rescue SystemExit + # exit IP + warn("Warning: " + $! + " on " + self.inspect) if $DEBUG + @wait_on_mainloop[1] = false + begin + self._eval_without_enc('exit') + rescue Exception + end + self.delete + rescue Exception => e + if $DEBUG + warn("Warning: " + e.class.inspect + + ((e.message.length > 0)? ' "' + e.message + '"': '') + + " on " + self.inspect) + end + @wait_on_mainloop[1] = false + return e + ensure + @wait_on_mainloop[1] = false + end + end + return + end + unless restart_on_dead + @wait_on_mainloop[1] = true @interp.mainloop(check_root) + @wait_on_mainloop[1] = false else begin + @wait_on_mainloop[1] = true loop do break unless self.alive? if check_root @@ -1605,6 +1756,8 @@ class MultiTkIp " exception (ignore) : ", $!.message, "\n"); end retry + ensure + @wait_on_mainloop[1] = false end end self @@ -2066,7 +2219,7 @@ end # Safe Base :: manipulating safe interpreter class MultiTkIp - def safeip_configure(slave, slot, value=None) + def safeip_configure(slot, value=None) # use for '-noStatics' option ==> {statics=>false} # for '-nestedLoadOk' option ==> {nested=>true} if slot.kind_of?(Hash) @@ -2111,22 +2264,22 @@ class MultiTkIp ret end - def safeip_delete(slave) + def safeip_delete ip = MultiTkIp.__getip ip._eval("::safe::interpDelete " + @ip_name) end - def safeip_add_to_access_path(slave, dir) + def safeip_add_to_access_path(dir) ip = MultiTkIp.__getip ip._eval("::safe::interpAddToAccessPath #{@ip_name} #{dir}") end - def safeip_find_in_access_path(slave, dir) + def safeip_find_in_access_path(dir) ip = MultiTkIp.__getip ip._eval("::safe::interpFindInAccessPath #{@ip_name} #{dir}") end - def safeip_set_log_cmd(slave, cmd = Proc.new) + def safeip_set_log_cmd(cmd = Proc.new) ip = MultiTkIp.__getip ip._eval("::safe::setLogCmd #{@ip_name} #{_get_eval_string(cmd)}") end diff --git a/ext/tk/lib/remote-tk.rb b/ext/tk/lib/remote-tk.rb index d09b2289e6..77dbacfb13 100644 --- a/ext/tk/lib/remote-tk.rb +++ b/ext/tk/lib/remote-tk.rb @@ -96,6 +96,8 @@ class RemoteTkIp @safe_level = [$SAFE] + @wait_on_mainloop = [true, false] + @cmd_queue = Queue.new =begin @@ -403,6 +405,12 @@ class RemoteTkIp def do_one_evant(flag = nil) fail RuntimeError, 'not support "do_one_event" on the remote interpreter' end + def mainloop_abort_on_exception + fail RuntimeError, 'not support "mainloop_abort_on_exception" on the remote interpreter' + end + def mainloop_abort_on_exception=(mode) + fail RuntimeError, 'not support "mainloop_abort_on_exception=" on the remote interpreter' + end def set_eventloop_tick(*args) fail RuntimeError, 'not support "set_eventloop_tick" on the remote interpreter' end @@ -421,24 +429,24 @@ class RemoteTkIp def get_eventloop_weight fail RuntimeError, 'not support "get_eventloop_weight" on the remote interpreter' end - def mainloop_abort_on_exception - fail RuntimeError, 'not support "mainloop_abort_on_exception" on the remote interpreter' - end - def mainloop_abort_on_exception=(*args) - fail RuntimeError, 'not support "mainloop_abort_on_exception=" on the remote interpreter' - end end class << RemoteTkIp - def mainloop + def mainloop(*args) fail RuntimeError, 'not support "mainloop" on the remote interpreter' end - def mainloop_watchdog + def mainloop_watchdog(*args) fail RuntimeError, 'not support "mainloop_watchdog" on the remote interpreter' end def do_one_evant(flag = nil) fail RuntimeError, 'not support "do_one_event" on the remote interpreter' end + def mainloop_abort_on_exception + fail RuntimeError, 'not support "mainloop_abort_on_exception" on the remote interpreter' + end + def mainloop_abort_on_exception=(mode) + fail RuntimeError, 'not support "mainloop_abort_on_exception=" on the remote interpreter' + end def set_eventloop_tick(*args) fail RuntimeError, 'not support "set_eventloop_tick" on the remote interpreter' end @@ -457,10 +465,4 @@ class << RemoteTkIp def get_eventloop_weight fail RuntimeError, 'not support "get_eventloop_weight" on the remote interpreter' end - def mainloop_abort_on_exception - fail RuntimeError, 'not support "mainloop_abort_on_exception" on the remote interpreter' - end - def mainloop_abort_on_exception=(*args) - fail RuntimeError, 'not support "mainloop_abort_on_exception=" on the remote interpreter' - end end diff --git a/ext/tk/lib/tk.rb b/ext/tk/lib/tk.rb index e6a4dd14e4..499d71d876 100644 --- a/ext/tk/lib/tk.rb +++ b/ext/tk/lib/tk.rb @@ -3756,15 +3756,24 @@ class TkWindowproc{ip.eval_proc{b.flash}}, +btns.each_with_index{|btn, idx| + # The scope of the eval-block of 'eval_proc' method is different from + # the enternal. If you want to pass local values to the eval-block, + # use arguments of eval_proc method. They are passed to block-arguments. + TkButton.new(:command=>proc{ip.eval_proc(btn){|b| b.flash}}, :text=>"flash button-#{idx}", :padx=>10).pack(:padx=>10, :pady=>2) } diff --git a/ext/tk/sample/tkoptdb-safeTk.rb b/ext/tk/sample/tkoptdb-safeTk.rb index f6c3ca4ee7..4b0816d304 100644 --- a/ext/tk/sample/tkoptdb-safeTk.rb +++ b/ext/tk/sample/tkoptdb-safeTk.rb @@ -37,6 +37,18 @@ ip = MultiTkIp.new_safeTk{ print "ip.eval_proc{$SAFE} ==> ", ip.eval_proc{$SAFE}, "\n" +print "\ncall 'ip.wait_on_mainloop = false'\n" +print "If 'ip.wait_on_mainloop? == true', ", + "when 'mainloop' is called on 'ip.eval_proc', ", + "'ip.eval_proc' does't return while the root window exists.\n", + "If you want to avoid that, set wait_on_mainloop to false. ", + "Then the mainloop in the eval_proc returns soon ", + "and the following steps are evaluated. \n", + "If you hate the both of them, use 'ip.bg_eval_proc' or ", + "wrap 'ip.eval_proc' by a thread.\n" + +ip.wait_on_mainloop = false + ret = ip.eval_proc{ # When a block is given to 'eval_proc' method, # the block is evaluated on the IP's current safe level. @@ -46,7 +58,7 @@ ret = ip.eval_proc{ load file } -print "ip.eval_proc{}, which includes insecure operiation in the given block, returns an exception object: ", ret.inspect, "\n" +print "\nip.eval_proc{}, which includes insecure operiation in the given block, returns an exception object: ", ret.inspect, "\n" print "If a proc object is given, the proc is evaluated on the safe-level which is kept on the proc :: ip.eval_proc( proc{$SAFE} ) ==> ", ip.eval_proc(proc{$SAFE}), "\n" -- cgit v1.2.3