yarv-diff:312
From: ko1 atdot.net
Date: 27 Feb 2006 02:53:23 -0000
Subject: [yarv-diff:312] r477 - in trunk: . lib lib/racc lib/rinda
Author: aamine Date: 2006-02-27 11:53:22 +0900 (Mon, 27 Feb 2006) New Revision: 477 Added: trunk/lib/README trunk/lib/gserver.rb trunk/lib/parsearg.rb trunk/lib/racc/ trunk/lib/racc/parser.rb trunk/lib/readbytes.rb trunk/lib/rinda/ trunk/lib/rinda/.document trunk/lib/rinda/rinda.rb trunk/lib/rinda/ring.rb trunk/lib/rinda/tuplespace.rb Modified: trunk/ChangeLog Log: * lib/README: imported from Ruby CVS trunk HEAD. * lib/gserver.rb: ditto. * lib/readbytes.rb: ditto. * lib/parsearg.rb: ditto. * lib/racc: ditto. * lib/rinda: ditto. Modified: trunk/ChangeLog =================================================================== --- trunk/ChangeLog 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/ChangeLog 2006-02-27 02:53:22 UTC (rev 477) @@ -4,6 +4,21 @@ # from Mon, 03 May 2004 01:24:19 +0900 # +2006-02-27(Mon) 11:53:07 +0900 Minero Aoki <aamine loveruby.net> + + * lib/README: imported from Ruby CVS trunk HEAD. + + * lib/gserver.rb: ditto. + + * lib/readbytes.rb: ditto. + + * lib/parsearg.rb: ditto. + + * lib/racc: ditto. + + * lib/rinda: ditto. + + 2006-02-27(Mon) 11:27:19 +0900 Minero Aoki <aamine loveruby.net> * lib/thread.rb (Queue#pop): faster code. [yarv-dev:973] Added: trunk/lib/README =================================================================== --- trunk/lib/README 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/README 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,96 @@ +English.rb lets Perl'ish global variables have English names +Env.rb loads importenv.rb +README this file +base64.rb encodes/decodes base64 (obsolete) +benchmark.rb a benchmark utility +cgi-lib.rb simple CGI support library (old style) +cgi.rb CGI support library +cgi/session.rb CGI session class +complex.rb complex number suppor +csv.rb CSV parser/generator +date.rb date object +date/format.rb date parsing and formatting +date2.rb date object (obsolete; use date) +debug.rb ruby debugger +delegate.rb delegates messages to other object +drb.rb distributed Ruby +e2mmap.rb exception utilities +erb.rb tiny eRuby library +eregex.rb extended regular expression (just a proof of concept) +fileutils.rb file utilities +finalize.rb adds finalizer to the object +find.rb traverses directory tree +forwardable.rb explicit delegation library +ftools.rb file tools +getoptlong.rb GNU getoptlong compatible +getopts.rb parses command line options (use getoptlong) +gserver.rb general TCP server +importenv.rb imports environment variables as global variables +ipaddr.rb defines the IPAddr class +irb.rb interactive ruby +jcode.rb Japanese text handling (replace String methods) +logger.rb simple logging utility +mailread.rb reads mail headers +mathn.rb extended math operation +matrix.rb matrix calculation library +mkmf.rb Makefile maker +monitor.rb exclusive region monitor for thread +mutex_m.rb mutex mixin +net/ftp.rb ftp access +net/http.rb HTTP access +net/imap.rb IMAP4 access +net/pop.rb POP3 access +net/protocol.rb abstract class for net library (DO NOT USE) +net/smtp.rb SMTP access +net/telnet.rb telnet library +observer.rb observer desing pattern library (provides Observable) +open-uri.rb easy-to-use network interface using URI and Net +open3.rb opens subprocess connection stdin/stdout/stderr +optparse.rb command line option analysis +ostruct.rb python style object +parsearg.rb argument parser using getopts +parsedate.rb parses date string +pathname.rb Object-Oriented Pathname Class +ping.rb checks whether host is up, using TCP echo. +pp.rb pretty print objects +prettyprint.rb pretty printing algorithm +profile.rb runs ruby profiler +profiler.rb ruby profiler module +pstore.rb persistent object strage using marshal +racc/parser.rb racc (Ruby yACC) runtime +rational.rb rational number support +rdoc source-code documentation tool +readbytes.rb define IO#readbytes +resolv-replace.rb replace Socket DNS by resolve.rb +resolv.rb DNS resolver in Ruby +rexml an XML parser for Ruby, in Ruby +rubyunit.rb original Ruby Unit testing framework +scanf.rb scanf for Ruby +set.rb defines the Set class +shell.rb runs commands and does pipeline operations like shell +shellwords.rb split into words like shell +singleton.rb singleton design pattern library +soap SOAP 1.1 implementation +sync.rb 2 phase lock +tempfile.rb temporary file with automatic removal +test/unit Ruby Unit Testing Framework +thread.rb thread support +thwait.rb thread syncronization class +time.rb RFC2822, RFC2616, ISO8601 style time formatting/parsing +timeout.rb provides timeout +tmpdir.rb retrieve temporary directory path +tracer.rb execution tracer +tsort.rb topological sorting +un.rb Utilities to replace common UNIX commands in Makefiles etc +uri.rb URI support +uri/ftp.rb ftp scheme support +uri/http.rb http scheme support +uri/https.rb https scheme support +uri/ldap.rb ldap scheme support +uri/mailto.rb mailto scheme support +weakref.rb weak reference class +webrick.rb WEB server toolkit +wsdl WSDL 1.1 implementation +xmlrpc XML-RPC implementation +xsd XML Schema Datatypes implementation +yaml.rb YAML implementation Added: trunk/lib/gserver.rb =================================================================== --- trunk/lib/gserver.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/gserver.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,249 @@ +# +# Copyright (C) 2001 John W. Small All Rights Reserved +# +# Author:: John W. Small +# Documentation:: Gavin Sinclair +# Licence:: Freeware. +# +# See the class GServer for documentation. +# + +require "socket" +require "thread" + +# +# +GServer+ implements a generic server, featuring thread pool management, simple logging, and +# multi-server management. See <tt>xmlrpc/httpserver.rb</tt> in the Ruby standard library for +# an example of +GServer+ in action. +# +# Any kind of application-level server can be implemented using this class. It accepts +# multiple simultaneous connections from clients, up to an optional maximum number. Several +# _services_ (i.e. one service per TCP port) can be run simultaneously, and stopped at any time +# through the class method <tt>GServer.stop(port)</tt>. All the threading issues are handled, +# saving you the effort. All events are optionally logged, but you can provide your own event +# handlers if you wish. +# +# === Example +# +# Using +GServer+ is simple. Below we implement a simple time server, run it, query it, and +# shut it down. Try this code in +irb+: +# +# require 'gserver' +# +# # +# # A server that returns the time in seconds since 1970. +# # +# class TimeServer < GServer +# def initialize(port=10001, *args) +# super(port, *args) +# end +# def serve(io) +# io.puts(Time.now.to_i) +# end +# end +# +# # Run the server with logging enabled (it's a separate thread). +# server = TimeServer.new +# server.audit = true # Turn logging on. +# server.start +# +# # *** Now point your browser to http://localhost:10001 to see it working *** +# +# # See if it's still running. +# GServer.in_service?(10001) # -> true +# server.stopped? # -> false +# +# # Shut the server down gracefully. +# server.shutdown +# +# # Alternatively, stop it immediately. +# GServer.stop(10001) +# # or, of course, "server.stop". +# +# All the business of accepting connections and exception handling is taken care of. All we +# have to do is implement the method that actually serves the client. +# +# === Advanced +# +# As the example above shows, the way to use +GServer+ is to subclass it to create a specific +# server, overriding the +serve+ method. You can override other methods as well if you wish, +# perhaps to collect statistics, or emit more detailed logging. +# +# connecting +# disconnecting +# starting +# stopping +# +# The above methods are only called if auditing is enabled. +# +# You can also override +log+ and +error+ if, for example, you wish to use a more sophisticated +# logging system. +# +class GServer + + DEFAULT_HOST = "127.0.0.1" + + def serve(io) + end + + @@services = {} # Hash of opened ports, i.e. services + @@servicesMutex = Mutex.new + + def GServer.stop(port, host = DEFAULT_HOST) + servicesMutex.synchronize { + services[host][port].stop + } + end + + def GServer.in_service?(port, host = DEFAULT_HOST) + services.has_key?(host) and + services[host].has_key?(port) + end + + def stop + @connectionsMutex.synchronize { + if @tcpServerThread + @tcpServerThread.raise "stop" + end + } + end + + def stopped? + @tcpServerThread == nil + end + + def shutdown + @shutdown = true + end + + def connections + @connections.size + end + + def join + @tcpServerThread.join if @tcpServerThread + end + + attr_reader :port, :host, :maxConnections + attr_accessor :stdlog, :audit, :debug + + def connecting(client) + addr = client.peeraddr + log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " + + "#{addr[2]}<#{addr[3]}> connect") + true + end + + def disconnecting(clientPort) + log("#{self.class.to_s} #{@host}:#{@port} " + + "client:#{clientPort} disconnect") + end + + protected :connecting, :disconnecting + + def starting() + log("#{self.class.to_s} #{@host}:#{@port} start") + end + + def stopping() + log("#{self.class.to_s} #{@host}:#{@port} stop") + end + + protected :starting, :stopping + + def error(detail) + log(detail.backtrace.join("\n")) + end + + def log(msg) + if @stdlog + @stdlog.puts("[#{Time.new.ctime}] %s" % msg) + @stdlog.flush + end + end + + protected :error, :log + + def initialize(port, host = DEFAULT_HOST, maxConnections = 4, + stdlog = $stderr, audit = false, debug = false) + @tcpServerThread = nil + @port = port + @host = host + @maxConnections = maxConnections + @connections = [] + @connectionsMutex = Mutex.new + @connectionsCV = ConditionVariable.new + @stdlog = stdlog + @audit = audit + @debug = debug + end + + def start(maxConnections = -1) + raise "running" if !stopped? + @shutdown = false + @maxConnections = maxConnections if maxConnections > 0 + servicesMutex.synchronize { + if GServer.in_service?(@port,@host) + raise "Port already in use: #{host}:#{@port}!" + end + @tcpServer = TCPServer.new(@host,@port) + @port = @tcpServer.addr[1] + @@services[@host] = {} unless services.has_key?( host) + @@services[@host][@port] = self; + } + @tcpServerThread = Thread.new { + begin + starting if @audit + while !@shutdown + @connectionsMutex.synchronize { + while @connections.size >= @maxConnections + @connectionsCV.wait(@connectionsMutex) + end + } + client = @tcpServer.accept + @connections << Thread.new(client) { |myClient| + begin + myPort = myClient.peeraddr[1] + serve(myClient) if !@audit or connecting(myClient) + rescue => detail + error(detail) if @debug + ensure + begin + myClient.close + rescue + end + @connectionsMutex.synchronize { + @connections.delete(Thread.current) + @connectionsCV.signal + } + disconnecting(myPort) if @audit + end + } + end + rescue => detail + error(detail) if @debug + ensure + begin + @tcpServer.close + rescue + end + if @shutdown + @connectionsMutex.synchronize { + while @connections.size > 0 + @connectionsCV.wait(@connectionsMutex) + end + } + else + @connections.each { |c| c.raise "stop" } + end + @tcpServerThread = nil + servicesMutex.synchronize { + services[ host].delete( port) + } + stopping if @audit + end + } + self + end + +end Added: trunk/lib/parsearg.rb =================================================================== --- trunk/lib/parsearg.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/parsearg.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,85 @@ +# +# parsearg.rb - parse arguments +# $Release Version: $ +# $Revision: 1.5 $ +# $Date: 2006/02/03 09:15:37 $ +# by Yasuo OHBA(SHL Japan Inc. Technology Dept.) +# +# -- +# +# +# + +warn "Warning:#{caller[0].sub(/:in `.*'\z/, '')}: parsearg is deprecated after Ruby 1.8.1; use optparse instead" + +$RCS_ID=%q$Header: /var/cvs/ruby/ruby/lib/parsearg.rb,v 1.5 2006/02/03 09:15:37 matz Exp $ + +require "getopts" + +def printUsageAndExit() + if $USAGE + eval($USAGE) + end + exit() +end + +def setParenthesis(ex, opt, c) + if opt != "" + ex = sprintf("%s$OPT_%s%s", ex, opt, c) + else + ex = sprintf("%s%s", ex, c) + end + return ex +end + +def setOrAnd(ex, opt, c) + if opt != "" + ex = sprintf("%s$OPT_%s %s%s ", ex, opt, c, c) + else + ex = sprintf("%s %s%s ", ex, c, c) + end + return ex +end + +def setExpression(ex, opt, op) + if !op + ex = sprintf("%s$OPT_%s", ex, opt) + return ex + end + case op.chr + when "(", ")" + ex = setParenthesis(ex, opt, op.chr) + when "|", "&" + ex = setOrAnd(ex, opt, op.chr) + else + return nil + end + return ex +end + +def parseArgs(argc, nopt, single_opts, *opts) + if (noOptions = getopts(single_opts, *opts)) == nil + printUsageAndExit() + end + if nopt + ex = nil + pos = 0 + for o in nopt.split(/[()|&]/) + pos += o.length + ex = setExpression(ex, o, nopt[pos]) + pos += 1 + end + begin + if !eval(ex) + printUsageAndExit() + end + rescue + print "Format Error!! : \"" + nopt + "\"\t[parseArgs]\n" + exit!(-1) + end + end + if ARGV.length < argc + printUsageAndExit() + end + return noOptions +end Added: trunk/lib/racc/parser.rb =================================================================== --- trunk/lib/racc/parser.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/racc/parser.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,461 @@ +# +# parser.rb +# +# Copyright (c) 1999-2004 Minero Aoki +# +# This program is free software. +# You can distribute/modify this program under the same terms of ruby. +# +# As a special exception, when this code is copied by Racc +# into a Racc output file, you may use that output file +# without restriction. +# +# $raccId: parser.rb,v 1.4 2003/11/03 13:41:47 aamine Exp $ +# +# NOTE: +# This file is part of the runtime library of Racc parser generator. +# If you want to generate your own parser, you must get Racc full package. +# Get it from: http://raa.ruby-lang.org/list.rhtml?name=racc +# + +unless defined?(NotImplementedError) + NotImplementedError = NotImplementError +end + +module Racc + class ParseError < StandardError; end +end +unless defined?(::ParseError) + ParseError = Racc::ParseError +end + + +module Racc + + unless defined?(Racc_No_Extentions) + Racc_No_Extentions = false + end + + class Parser + + Racc_Runtime_Version = '1.4.4' + Racc_Runtime_Revision = '$raccRevision: 1.4 $'.split[1] + + Racc_Runtime_Core_Version_R = '1.4.4' + Racc_Runtime_Core_Revision_R = '$raccRevision: 1.4 $'.split[1] + begin + require 'racc/cparse' + # Racc_Runtime_Core_Version_C = (defined in extension) + Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2] + unless new.respond_to?(:_racc_do_parse_c, true) + raise LoadError, 'old cparse.so' + end + if Racc_No_Extentions + raise LoadError, 'selecting ruby version of racc runtime core' + end + + Racc_Main_Parsing_Routine = :_racc_do_parse_c + Racc_YY_Parse_Method = :_racc_yyparse_c + Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C + Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C + Racc_Runtime_Type = 'c' + rescue LoadError + Racc_Main_Parsing_Routine = :_racc_do_parse_rb + Racc_YY_Parse_Method = :_racc_yyparse_rb + Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R + Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R + Racc_Runtime_Type = 'ruby' + end + + def Parser.racc_runtime_type + Racc_Runtime_Type + end + + private + + def _racc_setup + @yydebug = false unless self.class::Racc_debug_parser + @yydebug = false unless defined?(@yydebug) + if @yydebug + @racc_debug_out = $stderr unless defined?(@racc_debug_out) + @racc_debug_out ||= $stderr + end + arg = self.class::Racc_arg + arg[13] = true if arg.size < 14 + arg + end + + def _racc_init_sysvars + @racc_state = [0] + @racc_tstack = [] + @racc_vstack = [] + + @racc_t = nil + @racc_val = nil + + @racc_read_next = true + + @racc_user_yyerror = false + @racc_error_status = 0 + end + + ### + ### do_parse + ### + + def do_parse + __send__(Racc_Main_Parsing_Routine, _racc_setup(), false) + end + + def next_token + raise NotImplementedError, "#{self.class}\#next_token is not defined" + end + + def _racc_do_parse_rb( arg, in_debug ) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + + _racc_init_sysvars + tok = act = i = nil + nerr = 0 + + catch(:racc_end_parse) { + while true + if i = action_pointer[@racc_state[-1]] + if @racc_read_next + if @racc_t != 0 # not EOF + tok, @racc_val = next_token() + unless tok # EOF + @racc_t = 0 + else + @racc_t = (token_table[tok] or 1) # error token + end + racc_read_token(@racc_t, tok, @racc_val) if @yydebug + @racc_read_next = false + end + end + i += @racc_t + unless i >= 0 and + act = action_table[i] and + action_check[i] == @racc_state[-1] + act = action_default[@racc_state[-1]] + end + else + act = action_default[@racc_state[-1]] + end + while act = _racc_evalact(act, arg) + ; + end + end + } + end + + ### + ### yyparse + ### + + def yyparse( recv, mid ) + __send__(Racc_YY_Parse_Method, recv, mid, _racc_setup(), true) + end + + def _racc_yyparse_rb( recv, mid, arg, c_debug ) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + + _racc_init_sysvars + tok = nil + act = nil + i = nil + nerr = 0 + + catch(:racc_end_parse) { + until i = action_pointer[@racc_state[-1]] + while act = _racc_evalact(action_default[@racc_state[-1]], arg) + ; + end + end + recv.__send__(mid) do |tok, val| +# $stderr.puts "rd: tok=#{tok}, val=#{val}" + unless tok + @racc_t = 0 + else + @racc_t = (token_table[tok] or 1) # error token + end + @racc_val = val + @racc_read_next = false + + i += @racc_t + unless i >= 0 and + act = action_table[i] and + action_check[i] == @racc_state[-1] + act = action_default[@racc_state[-1]] +# $stderr.puts "02: act=#{act}" +# $stderr.puts "curstate=#{@racc_state[-1]}" + else +# $stderr.puts "01: act=#{act}" + end + + while act = _racc_evalact(act, arg) + ; + end + + while not (i = action_pointer[@racc_state[-1]]) or + not @racc_read_next or + @racc_t == 0 # $ + unless i and i += @racc_t and + i >= 0 and + act = action_table[i] and + action_check[i] == @racc_state[-1] + act = action_default[@racc_state[-1]] +# $stderr.puts "04: act=#{act}" + else +# $stderr.puts "03: act=#{act}" + end + while act = _racc_evalact(act, arg) + ; + end + end + end + } + end + + ### + ### common + ### + + def _racc_evalact( act, arg ) +# $stderr.puts "ea: act=#{act}" + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg +nerr = 0 # tmp + + if act > 0 and act < shift_n + # + # shift + # + if @racc_error_status > 0 + @racc_error_status -= 1 unless @racc_t == 1 # error token + end + @racc_vstack.push @racc_val + @racc_state.push act + @racc_read_next = true + if @yydebug + @racc_tstack.push @racc_t + racc_shift @racc_t, @racc_tstack, @racc_vstack + end + + elsif act < 0 and act > -reduce_n + # + # reduce + # + code = catch(:racc_jump) { + @racc_state.push _racc_do_reduce(arg, act) + false + } + if code + case code + when 1 # yyerror + @racc_user_yyerror = true # user_yyerror + return -reduce_n + when 2 # yyaccept + return shift_n + else + raise RuntimeError, '[Racc Bug] unknown jump code' + end + end + + elsif act == shift_n + # + # accept + # + racc_accept if @yydebug + throw :racc_end_parse, @racc_vstack[0] + + elsif act == -reduce_n + # + # error + # + case @racc_error_status + when 0 + unless arg[21] # user_yyerror + nerr += 1 + on_error @racc_t, @racc_val, @racc_vstack + end + when 3 + if @racc_t == 0 # is $ + throw :racc_end_parse, nil + end + @racc_read_next = true + end + @racc_user_yyerror = false + @racc_error_status = 3 + while true + if i = action_pointer[@racc_state[-1]] + i += 1 # error token + if i >= 0 and + (act = action_table[i]) and + action_check[i] == @racc_state[-1] + break + end + end + + throw :racc_end_parse, nil if @racc_state.size <= 1 + @racc_state.pop + @racc_vstack.pop + if @yydebug + @racc_tstack.pop + racc_e_pop @racc_state, @racc_tstack, @racc_vstack + end + end + return act + + else + raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}" + end + + racc_next_state(@racc_state[-1], @racc_state) if @yydebug + + nil + end + + def _racc_do_reduce( arg, act ) + action_table, action_check, action_default, action_pointer, + goto_table, goto_check, goto_default, goto_pointer, + nt_base, reduce_table, token_table, shift_n, + reduce_n, use_result, * = arg + state = @racc_state + vstack = @racc_vstack + tstack = @racc_tstack + + i = act * -3 + len = reduce_table[i] + reduce_to = reduce_table[i+1] + method_id = reduce_table[i+2] + void_array = [] + + tmp_t = tstack[-len, len] if @yydebug + tmp_v = vstack[-len, len] + tstack[-len, len] = void_array if @yydebug + vstack[-len, len] = void_array + state[-len, len] = void_array + + # tstack must be updated AFTER method call + if use_result + vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) + else + vstack.push __send__(method_id, tmp_v, vstack) + end + tstack.push reduce_to + + racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug + + k1 = reduce_to - nt_base + if i = goto_pointer[k1] + i += state[-1] + if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 + return curstate + end + end + goto_default[k1] + end + + def on_error( t, val, vstack ) + raise ParseError, sprintf("\nparse error on value %s (%s)", + val.inspect, token_to_str(t) || '?') + end + + def yyerror + throw :racc_jump, 1 + end + + def yyaccept + throw :racc_jump, 2 + end + + def yyerrok + @racc_error_status = 0 + end + + # + # for debugging output + # + + def racc_read_token( t, tok, val ) + @racc_debug_out.print 'read ' + @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') ' + @racc_debug_out.puts val.inspect + @racc_debug_out.puts + end + + def racc_shift( tok, tstack, vstack ) + @racc_debug_out.puts "shift #{racc_token2str tok}" + racc_print_stacks tstack, vstack + @racc_debug_out.puts + end + + def racc_reduce( toks, sim, tstack, vstack ) + out = @racc_debug_out + out.print 'reduce ' + if toks.empty? + out.print ' <none>' + else + toks.each {|t| out.print ' ', racc_token2str(t) } + end + out.puts " --> #{racc_token2str(sim)}" + + racc_print_stacks tstack, vstack + @racc_debug_out.puts + end + + def racc_accept + @racc_debug_out.puts 'accept' + @racc_debug_out.puts + end + + def racc_e_pop( state, tstack, vstack ) + @racc_debug_out.puts 'error recovering mode: pop token' + racc_print_states state + racc_print_stacks tstack, vstack + @racc_debug_out.puts + end + + def racc_next_state( curstate, state ) + @racc_debug_out.puts "goto #{curstate}" + racc_print_states state + @racc_debug_out.puts + end + + def racc_print_stacks( t, v ) + out = @racc_debug_out + out.print ' [' + t.each_index do |i| + out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' + end + out.puts ' ]' + end + + def racc_print_states( s ) + out = @racc_debug_out + out.print ' [' + s.each {|st| out.print ' ', st } + out.puts ' ]' + end + + def racc_token2str( tok ) + self.class::Racc_token_to_s_table[tok] or + raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string" + end + + def token_to_str( t ) + self.class::Racc_token_to_s_table[t] + end + + end + +end Added: trunk/lib/readbytes.rb =================================================================== --- trunk/lib/readbytes.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/readbytes.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,36 @@ +# readbytes.rb +# +# add IO#readbytes, which reads fixed sized data. +# it guarantees read data size. + +class TruncatedDataError<IOError + def initialize(mesg, data) + @data = data + super(mesg) + end + attr_reader :data +end + +class IO + def readbytes(n) + str = read(n) + if str == nil + raise EOFError, "End of file reached" + end + if str.size < n + raise TruncatedDataError.new("data truncated", str) + end + str + end +end + +if __FILE__ == $0 + begin + loop do + print STDIN.readbytes(6) + end + rescue TruncatedDataError + p $!.data + raise + end +end Added: trunk/lib/rinda/.document =================================================================== --- trunk/lib/rinda/.document 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/rinda/.document 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,3 @@ +rinda.rb +ring.rb +tuplespace.rb Added: trunk/lib/rinda/rinda.rb =================================================================== --- trunk/lib/rinda/rinda.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/rinda/rinda.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,283 @@ +require 'drb/drb' +require 'thread' + +## +# A module to implement the Linda distributed computing paradigm in Ruby. +# +# Rinda is part of DRb (dRuby). +# +# == Example(s) +# +# See the sample/drb/ directory in the Ruby distribution, from 1.8.2 onwards. +# +#-- +# TODO +# == Introduction to Linda/rinda? +# +# == Why is this library separate from DRb? + +module Rinda + + ## + # Rinda error base class + + class RindaError < RuntimeError; end + + ## + # Raised when a hash-based tuple has an invalid key. + + class InvalidHashTupleKey < RindaError; end + + ## + # Raised when trying to use a canceled tuple. + + class RequestCanceledError < ThreadError; end + + ## + # Raised when trying to use an expired tuple. + + class RequestExpiredError < ThreadError; end + + ## + # A tuple is the elementary object in Rinda programming. + # Tuples may be matched against templates if the tuple and + # the template are the same size. + + class Tuple + + ## + # Creates a new Tuple from +ary_or_hash+ which must be an Array or Hash. + + def initialize(ary_or_hash) + if hash?(ary_or_hash) + init_with_hash(ary_or_hash) + else + init_with_ary(ary_or_hash) + end + end + + ## + # The number of elements in the tuple. + + def size + @tuple.size + end + + ## + # Accessor method for elements of the tuple. + + def [](k) + @tuple[k] + end + + ## + # Fetches item +k+ from the tuple. + + def fetch(k) + @tuple.fetch(k) + end + + ## + # Iterate through the tuple, yielding the index or key, and the + # value, thus ensuring arrays are iterated similarly to hashes. + + def each # FIXME + if Hash === @tuple + @tuple.each { |k, v| yield(k, v) } + else + @tuple.each_with_index { |v, k| yield(k, v) } + end + end + + ## + # Return the tuple itself + def value + @tuple + end + + private + + def hash?(ary_or_hash) + ary_or_hash.respond_to?(:keys) + end + + ## + # Munges +ary+ into a valid Tuple. + + def init_with_ary(ary) + @tuple = Array.new(ary.size) + @tuple.size.times do |i| + @tuple[i] = ary[i] + end + end + + ## + # Ensures +hash+ is a valid Tuple. + + def init_with_hash(hash) + @tuple = Hash.new + hash.each do |k, v| + raise InvalidHashTupleKey unless String === k + @tuple[k] = v + end + end + + end + + ## + # Templates are used to match tuples in Rinda. + + class Template < Tuple + + ## + # Matches this template against +tuple+. The +tuple+ must be the same + # size as the template. An element with a +nil+ value in a template acts + # as a wildcard, matching any value in the corresponding position in the + # tuple. Elements of the template match the +tuple+ if the are #== or + # #===. + # + # Template.new([:foo, 5]).match Tuple.new([:foo, 5]) # => true + # Template.new([:foo, nil]).match Tuple.new([:foo, 5]) # => true + # Template.new([String]).match Tuple.new(['hello']) # => true + # + # Template.new([:foo]).match Tuple.new([:foo, 5]) # => false + # Template.new([:foo, 6]).match Tuple.new([:foo, 5]) # => false + # Template.new([:foo, nil]).match Tuple.new([:foo]) # => false + # Template.new([:foo, 6]).match Tuple.new([:foo]) # => false + + def match(tuple) + return false unless tuple.respond_to?(:size) + return false unless tuple.respond_to?(:fetch) + return false unless self.size == tuple.size + each do |k, v| + begin + it = tuple.fetch(k) + rescue + return false + end + next if v.nil? + next if v == it + next if v === it + return false + end + return true + end + + ## + # Alias for #match. + + def ===(tuple) + match(tuple) + end + + end + + ## + # <i>Documentation?</i> + + class DRbObjectTemplate + + ## + # Creates a new DRbObjectTemplate that will match against +uri+ and +ref+. + + def initialize(uri=nil, ref=nil) + @drb_uri = uri + @drb_ref = ref + end + + ## + # This DRbObjectTemplate matches +ro+ if the remote object's drburi and + # drbref are the same. +nil+ is used as a wildcard. + + def ===(ro) + return true if super(ro) + unless @drb_uri.nil? + return false unless (@drb_uri === ro.__drburi rescue false) + end + unless @drb_ref.nil? + return false unless (@drb_ref === ro.__drbref rescue false) + end + true + end + + end + + ## + # TupleSpaceProxy allows a remote Tuplespace to appear as local. + + class TupleSpaceProxy + + ## + # Creates a new TupleSpaceProxy to wrap +ts+. + + def initialize(ts) + @ts = ts + end + + ## + # Adds +tuple+ to the proxied TupleSpace. See TupleSpace#write. + + def write(tuple, sec=nil) + @ts.write(tuple, sec) + end + + ## + # Takes +tuple+ from the proxied TupleSpace. See TupleSpace#take. + + def take(tuple, sec=nil, &block) + port = [] + @ts.move(DRbObject.new(port), tuple, sec, &block) + port[0] + end + + ## + # Reads +tuple+ from the proxied TupleSpace. See TupleSpace#read. + + def read(tuple, sec=nil, &block) + @ts.read(tuple, sec, &block) + end + + ## + # Reads all tuples matching +tuple+ from the proxied TupleSpace. See + # TupleSpace#read_all. + + def read_all(tuple) + @ts.read_all(tuple) + end + + ## + # Registers for notifications of event +ev+ on the proxied TupleSpace. + # See TupleSpace#notify + + def notify(ev, tuple, sec=nil) + @ts.notify(ev, tuple, sec) + end + + end + + ## + # An SimpleRenewer allows a TupleSpace to check if a TupleEntry is still + # alive. + + class SimpleRenewer + + include DRbUndumped + + ## + # Creates a new SimpleRenewer that keeps an object alive for another +sec+ + # seconds. + + def initialize(sec=180) + @sec = sec + end + + ## + # Called by the TupleSpace to check if the object is still alive. + + def renew + @sec + end + end + +end + Added: trunk/lib/rinda/ring.rb =================================================================== --- trunk/lib/rinda/ring.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/rinda/ring.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,271 @@ +# +# Note: Rinda::Ring API is unstable. +# +require 'drb/drb' +require 'rinda/rinda' +require 'thread' + +module Rinda + + ## + # The default port Ring discovery will use. + + Ring_PORT = 7647 + + ## + # A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts. + # Service location uses the following steps: + # + # 1. A RingServer begins listening on the broadcast UDP address. + # 2. A RingFinger sends a UDP packet containing the DRb URI where it will + # listen for a reply. + # 3. The RingServer recieves the UDP packet and connects back to the + # provided DRb URI with the DRb service. + + class RingServer + + include DRbUndumped + + ## + # Advertises +ts+ on the UDP broadcast address at +port+. + + def initialize(ts, port=Ring_PORT) + @ts = ts + @soc = UDPSocket.open + @soc.bind('', port) + @w_service = write_service + @r_service = reply_service + end + + ## + # Creates a thread that picks up UDP packets and passes them to do_write + # for decoding. + + def write_service + Thread.new do + loop do + msg = @soc.recv(1024) + do_write(msg) + end + end + end + + ## + # Extracts the response URI from +msg+ and adds it to TupleSpace where it + # will be picked up by +reply_service+ for notification. + + def do_write(msg) + Thread.new do + begin + tuple, sec = Marshal.load(msg) + @ts.write(tuple, sec) + rescue + end + end + end + + ## + # Creates a thread that notifies waiting clients from the TupleSpace. + + def reply_service + Thread.new do + loop do + do_reply + end + end + end + + ## + # Pulls lookup tuples out of the TupleSpace and sends their DRb object the + # address of the local TupleSpace. + + def do_reply + tuple = @ts.take([:lookup_ring, DRbObject]) + Thread.new { tuple[1].call(@ts) rescue nil} + rescue + end + + end + + ## + # RingFinger is used by RingServer clients to discover the RingServer's + # TupleSpace. Typically, all a client needs to do is call + # RingFinger.primary to retrieve the remote TupleSpace, which it can then + # begin using. + + class RingFinger + + @@broadcast_list = ['<broadcast>', 'localhost'] + + @@finger = nil + + ## + # Creates a singleton RingFinger and looks for a RingServer. Returns the + # created RingFinger. + + def self.finger + unless @@finger + @@finger = self.new + finger.lookup_ring_any + end + @@finger + end + + ## + # Returns the first advertised TupleSpace. + + def self.primary + finger.primary + end + + ## + # Contains all discoverd TupleSpaces except for the primary. + + def self.to_a + finger.to_a + end + + ## + # The list of addresses where RingFinger will send query packets. + + attr_accessor :broadcast_list + + ## + # The port that RingFinger will send query packets to. + + attr_accessor :port + + ## + # Contain the first advertised TupleSpace after lookup_ring_any is called. + + attr_accessor :primary + + ## + # Creates a new RingFinger that will look for RingServers at +port+ on + # the addresses in +broadcast_list+. + + def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT) + @broadcast_list = broadcast_list || ['localhost'] + @port = port + @primary = nil + @rings = [] + end + + ## + # Contains all discovered TupleSpaces except for the primary. + + def to_a + @rings + end + + ## + # Iterates over all discovered TupleSpaces starting with the primary. + + def each + lookup_ring_any unless @primary + return unless @primary + yield(@primary) + @rings.each { |x| yield(x) } + end + + ## + # Looks up RingServers waiting +timeout+ seconds. RingServers will be + # given +block+ as a callback, which will be called with the remote + # TupleSpace. + + def lookup_ring(timeout=5, &block) + return lookup_ring_any(timeout) unless block_given? + + msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout]) + @broadcast_list.each do |it| + soc = UDPSocket.open + begin + soc.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true) + soc.send(msg, 0, it, @port) + rescue + nil + ensure + soc.close + end + end + sleep(timeout) + end + + ## + # Returns the first found remote TupleSpace. Any further recovered + # TupleSpaces can be found by calling +to_a+. + + def lookup_ring_any(timeout=5) + queue = Queue.new + + th = Thread.new do + self.lookup_ring(timeout) do |ts| + queue.push(ts) + end + queue.push(nil) + while it = queue.pop + @rings.push(it) + end + end + + @primary = queue.pop + raise('RingNotFound') if @primary.nil? + @primary + end + + end + + ## + # RingProvider uses a RingServer advertised TupleSpace as a name service. + # TupleSpace clients can register themselves with the remote TupleSpace and + # look up other provided services via the remote TupleSpace. + # + # Services are registered with a tuple of the format [:name, klass, + # DRbObject, description]. + + class RingProvider + + ## + # Creates a RingProvider that will provide a +klass+ service running on + # +front+, with a +description+. +renewer+ is optional. + + def initialize(klass, front, desc, renewer = nil) + @tuple = [:name, klass, front, desc] + @renewer = renewer || Rinda::SimpleRenewer.new + end + + ## + # Advertises this service on the primary remote TupleSpace. + + def provide + ts = Rinda::RingFinger.primary + ts.write(@tuple, @renewer) + end + + end + +end + +if __FILE__ == $0 + DRb.start_service + case ARGV.shift + when 's' + require 'rinda/tuplespace' + ts = Rinda::TupleSpace.new + place = Rinda::RingServer.new(ts) + $stdin.gets + when 'w' + finger = Rinda::RingFinger.new(nil) + finger.lookup_ring do |ts| + p ts + ts.write([:hello, :world]) + end + when 'r' + finger = Rinda::RingFinger.new(nil) + finger.lookup_ring do |ts| + p ts + p ts.take([nil, nil]) + end + end +end + Added: trunk/lib/rinda/tuplespace.rb =================================================================== --- trunk/lib/rinda/tuplespace.rb 2006-02-27 02:27:32 UTC (rev 476) +++ trunk/lib/rinda/tuplespace.rb 2006-02-27 02:53:22 UTC (rev 477) @@ -0,0 +1,589 @@ +require 'monitor' +require 'thread' +require 'drb/drb' +require 'rinda/rinda' + +module Rinda + + ## + # A TupleEntry is a Tuple (i.e. a possible entry in some Tuplespace) + # together with expiry and cancellation data. + + class TupleEntry + + include DRbUndumped + + attr_accessor :expires + + ## + # Creates a TupleEntry based on +ary+ with an optional renewer or expiry + # time +sec+. + # + # A renewer must implement the +renew+ method which returns a Numeric, + # nil, or true to indicate when the tuple has expired. + + def initialize(ary, sec=nil) + @cancel = false + @expires = nil + @tuple = make_tuple(ary) + @renewer = nil + renew(sec) + end + + ## + # Marks this TupleEntry as canceled. + + def cancel + @cancel = true + end + + ## + # A TupleEntry is dead when it is canceled or expired. + + def alive? + !canceled? && !expired? + end + + ## + # Return the object which makes up the tuple itself: the Array + # or Hash. + + def value; @tuple.value; end + + ## + # Returns the canceled status. + + def canceled?; @cancel; end + + ## + # Has this tuple expired? (true/false). + # + # A tuple has expired when its expiry timer based on the +sec+ argument to + # #initialize runs out. + + def expired? + return true unless @expires + return false if @expires > Time.now + return true if @renewer.nil? + renew(@renewer) + return true unless @expires + return @expires < Time.now + end + + ## + # Reset the expiry time according to +sec_or_renewer+. + # + # +nil+:: it is set to expire in the far future. + # +false+:: it has expired. + # Numeric:: it will expire in that many seconds. + # + # Otherwise the argument refers to some kind of renewer object + # which will reset its expiry time. + + def renew(sec_or_renewer) + sec, @renewer = get_renewer(sec_or_renewer) + @expires = make_expires(sec) + end + + ## + # Returns an expiry Time based on +sec+ which can be one of: + # Numeric:: +sec+ seconds into the future + # +true+:: the expiry time is the start of 1970 (i.e. expired) + # +nil+:: it is Tue Jan 19 03:14:07 GMT Standard Time 2038 (i.e. when + # UNIX clocks will die) + + def make_expires(sec=nil) + case sec + when Numeric + Time.now + sec + when true + Time.at(1) + when nil + Time.at(2**31-1) + end + end + + ## + # Retrieves +key+ from the tuple. + + def [](key) + @tuple[key] + end + + ## + # Fetches +key+ from the tuple. + + def fetch(key) + @tuple.fetch(key) + end + + ## + # The size of the tuple. + + def size + @tuple.size + end + + ## + # Creates a Rinda::Tuple for +ary+. + + def make_tuple(ary) + Rinda::Tuple.new(ary) + end + + private + + ## + # Returns a valid argument to make_expires and the renewer or nil. + # + # Given +true+, +nil+, or Numeric, returns that value and +nil+ (no actual + # renewer). Otherwise it returns an expiry value from calling +it.renew+ + # and the renewer. + + def get_renewer(it) + case it + when Numeric, true, nil + return it, nil + else + begin + return it.renew, it + rescue Exception + return it, nil + end + end + end + + end + + ## + # A TemplateEntry is a Template together with expiry and cancellation data. + + class TemplateEntry < TupleEntry + ## + # Matches this TemplateEntry against +tuple+. See Template#match for + # details on how a Template matches a Tuple. + + def match(tuple) + @tuple.match(tuple) + end + + alias === match + + def make_tuple(ary) # :nodoc: + Rinda::Template.new(ary) + end + + end + + ## + # <i>Documentation?</i> + + class WaitTemplateEntry < TemplateEntry + + attr_reader :found + + def initialize(place, ary, expires=nil) + super(ary, expires) + @place = place + @cond = place.new_cond + @found = nil + end + + def cancel + super + signal + end + + def wait + @cond.wait + end + + def read(tuple) + @found = tuple + signal + end + + def signal + @place.synchronize do + @cond.signal + end + end + + end + + ## + # A NotifyTemplateEntry is returned by TupleSpace#notify and is notified of + # TupleSpace changes. You may receive either your subscribed event or the + # 'close' event when iterating over notifications. + # + # See TupleSpace#notify_event for valid notification types. + # + # == Example + # + # ts = Rinda::TupleSpace.new + # observer = ts.notify 'write', [nil] + # + # Thread.start do + # observer.each { |t| p t } + # end + # + # 3.times { |i| ts.write [i] } + # + # Outputs: + # + # ['write', [0]] + # ['write', [1]] + # ['write', [2]] + + class NotifyTemplateEntry < TemplateEntry + + ## + # Creates a new NotifyTemplateEntry that watches +place+ for +event+s that + # match +tuple+. + + def initialize(place, event, tuple, expires=nil) + ary = [event, Rinda::Template.new(tuple)] + super(ary, expires) + @queue = Queue.new + @done = false + end + + ## + # Called by TupleSpace to notify this NotifyTemplateEntry of a new event. + + def notify(ev) + @queue.push(ev) + end + + ## + # Retrieves a notification. Raises RequestExpiredError when this + # NotifyTemplateEntry expires. + + def pop + raise RequestExpiredError if @done + it = @queue.pop + @done = true if it[0] == 'close' + return it + end + + ## + # Yields event/tuple pairs until this NotifyTemplateEntry expires. + + def each # :yields: event, tuple + while !@done + it = pop + yield(it) + end + rescue + ensure + cancel + end + + end + + ## + # TupleBag is an unordered collection of tuples. It is the basis + # of Tuplespace. + + class TupleBag + + def initialize # :nodoc: + @hash = {} + end + + ## + # +true+ if the TupleBag to see if it has any expired entries. + + def has_expires? + @hash.each do |k, v| + v.each do |tuple| + return true if tuple.expires + end + end + false + end + + ## + # Add +ary+ to the TupleBag. + + def push(ary) + size = ary.size + @hash[size] ||= [] + @hash[size].push(ary) + end + + ## + # Removes +ary+ from the TupleBag. + + def delete(ary) + size = ary.size + @hash.fetch(size, []).delete(ary) + end + + ## + # Finds all live tuples that match +template+. + + def find_all(template) + @hash.fetch(template.size, []).find_all do |tuple| + tuple.alive? && template.match(tuple) + end + end + + ## + # Finds a live tuple that matches +template+. + + def find(template) + @hash.fetch(template.size, []).find do |tuple| + tuple.alive? && template.match(tuple) + end + end + + ## + # Finds all tuples in the TupleBag which when treated as templates, match + # +tuple+ and are alive. + + def find_all_template(tuple) + @hash.fetch(tuple.size, []).find_all do |template| + template.alive? && template.match(tuple) + end + end + + ## + # Delete tuples which dead tuples from the TupleBag, returning the deleted + # tuples. + + def delete_unless_alive + deleted = [] + @hash.keys.each do |size| + ary = [] + @hash[size].each do |tuple| + if tuple.alive? + ary.push(tuple) + else + deleted.push(tuple) + end + end + @hash[size] = ary + end + deleted + end + + end + + ## + # The Tuplespace manages access to the tuples it contains, + # ensuring mutual exclusion requirements are met. + # + # The +sec+ option for the write, take, move, read and notify methods may + # either be a number of seconds or a Renewer object. + + class TupleSpace + + include DRbUndumped + include MonitorMixin + + ## + # Creates a new TupleSpace. +period+ is used to control how often to look + # for dead tuples after modifications to the TupleSpace. + # + # If no dead tuples are found +period+ seconds after the last + # modification, the TupleSpace will stop looking for dead tuples. + + def initialize(period=60) + super() + @bag = TupleBag.new + @read_waiter = TupleBag.new + @take_waiter = TupleBag.new + @notify_waiter = TupleBag.new + @period = period + @keeper = nil + end + + ## + # Adds +tuple+ + + def write(tuple, sec=nil) + entry = TupleEntry.new(tuple, sec) + start_keeper + synchronize do + if entry.expired? + @read_waiter.find_all_template(entry).each do |template| + template.read(tuple) + end + notify_event('write', entry.value) + notify_event('delete', entry.value) + else + @bag.push(entry) + @read_waiter.find_all_template(entry).each do |template| + template.read(tuple) + end + @take_waiter.find_all_template(entry).each do |template| + template.signal + end + notify_event('write', entry.value) + end + end + entry + end + + ## + # Removes +tuple+ + + def take(tuple, sec=nil, &block) + move(nil, tuple, sec, &block) + end + + ## + # Moves +tuple+ to +port+. + + def move(port, tuple, sec=nil) + template = WaitTemplateEntry.new(self, tuple, sec) + yield(template) if block_given? + start_keeper + synchronize do + entry = @bag.find(template) + if entry + port.push(entry.value) if port + @bag.delete(entry) + notify_event('take', entry.value) + return entry.value + end + raise RequestExpiredError if template.expired? + + begin + @take_waiter.push(template) + while true + raise RequestCanceledError if template.canceled? + raise RequestExpiredError if template.expired? + entry = @bag.find(template) + if entry + port.push(entry.value) if port + @bag.delete(entry) + notify_event('take', entry.value) + return entry.value + end + template.wait + end + ensure + @take_waiter.delete(template) + end + end + end + + ## + # Reads +tuple+, but does not remove it. + + def read(tuple, sec=nil) + template = WaitTemplateEntry.new(self, tuple, sec) + yield(template) if block_given? + start_keeper + synchronize do + entry = @bag.find(template) + return entry.value if entry + raise RequestExpiredError if template.expired? + + begin + @read_waiter.push(template) + template.wait + raise RequestCanceledError if template.canceled? + raise RequestExpiredError if template.expired? + return template.found + ensure + @read_waiter.delete(template) + end + end + end + + ## + # Returns all tuples matching +tuple+. Does not remove the found tuples. + + def read_all(tuple) + template = WaitTemplateEntry.new(self, tuple, nil) + synchronize do + entry = @bag.find_all(template) + entry.collect do |e| + e.value + end + end + end + + ## + # Registers for notifications of +event+. Returns a NotifyTemplateEntry. + # See NotifyTemplateEntry for examples of how to listen for notifications. + # + # +event+ can be: + # 'write':: A tuple was added + # 'take':: A tuple was taken or moved + # 'delete':: A tuple was lost after being overwritten or expiring + # + # The TupleSpace will also notify you of the 'close' event when the + # NotifyTemplateEntry has expired. + + def notify(event, tuple, sec=nil) + template = NotifyTemplateEntry.new(self, event, tuple, sec) + synchronize do + @notify_waiter.push(template) + end + template + end + + private + + ## + # Removes dead tuples. + + def keep_clean + synchronize do + @read_waiter.delete_unless_alive.each do |e| + e.signal + end + @take_waiter.delete_unless_alive.each do |e| + e.signal + end + @notify_waiter.delete_unless_alive.each do |e| + e.notify(['close']) + end + @bag.delete_unless_alive.each do |e| + notify_event('delete', e.value) + end + end + end + + ## + # Notifies all registered listeners for +event+ of a status change of + # +tuple+. + + def notify_event(event, tuple) + ev = [event, tuple] + @notify_waiter.find_all_template(ev).each do |template| + template.notify(ev) + end + end + + ## + # Creates a thread that scans the tuplespace for expired tuples. + + def start_keeper + return if @keeper && @keeper.alive? + @keeper = Thread.new do + while need_keeper? + keep_clean + sleep(@period) + end + end + end + + ## + # Checks the tuplespace to see if it needs cleaning. + + def need_keeper? + return true if @bag.has_expires? + return true if @read_waiter.has_expires? + return true if @take_waiter.has_expires? + return true if @notify_waiter.has_expires? + end + + end + +end + -- ML: yarv-diff quickml.atdot.net Info: http://www.atdot.net/~ko1/quickml