r9p

git clone git://oldgit.suckless.org/r9p/
Log | Files | Refs

commit b122f0a35779899572a459d53d75e2d6a9e2327a
Author: Kris Maglione <jg@suckless.org>
Date:   Wed, 14 Mar 2007 18:55:34 -0400

Initial commit.

Diffstat:
r9p.rb | 15+++++++++++++++
r9p/PERMISSIONS | 12++++++++++++
r9p/client.rb | 274+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
r9p/fcall.rb | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
r9p/ixp.rb | 0
r9p/mux.rb | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
r9p/qid.rb | 45+++++++++++++++++++++++++++++++++++++++++++++
r9p/serial.rb | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
r9p/stat.rb | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
r9p/test.rb | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
r9p/thread.rb | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 1180 insertions(+), 0 deletions(-)

diff --git a/r9p.rb b/r9p.rb @@ -0,0 +1,15 @@ +$debug ||= 0 + +module R9P + NoFid = ~0 + NoTag = ~0 + P9Version = '9P2000' +end + +require 'r9p/thread.rb' +require 'r9p/mux.rb' +require 'r9p/serial.rb' +require 'r9p/qid.rb' +require 'r9p/stat.rb' +require 'r9p/fcall.rb' +require 'r9p/client.rb' diff --git a/r9p/PERMISSIONS b/r9p/PERMISSIONS @@ -0,0 +1,12 @@ +Copyright (C) 2007 Kris Maglione <fbsdaemon@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. diff --git a/r9p/client.rb b/r9p/client.rb @@ -0,0 +1,274 @@ +# Copyright (C) 2007 Kris Maglione +# See PERMISSIONS + +module R9P + class Exception <Exception + end + class ProtocolException <Exception + end + class Client + RootFid = 0 + module Constants + OREAD = 0x00 + OWRITE = 0x01 + ORDWR = 0x02 + OEXEC = 0x03 + OEXCL = 0x04 + OTRUNC = 0x10 + OREXEC = 0x20 + ORCLOSE = 0x40 + OAPPEND = 0x80 + end + include Constants + include Qlock + attr_reader :msize + + def initialize(con, root = nil) + qlinit + @lastfid = RootFid + @fids = [] + @files = {} + @mux = Mux.new(con, :maxtag => 256) { |dat| + fcall = Fcall.from dat + } + + resp = dorpc Fcall::Tversion, :version => P9Version, :msize => 65535 + if resp.version != P9Version + throw ProtocolException, "Can't speak 9P version '#{resp.version}'" + end + @msize = resp.msize + + dorpc Fcall::Tattach, :fid => RootFid, :afid => NoFid, :uname => ENV['USER'], :aname => '' + + if root + path = root.split('/').reject{|v| v=='' } + resp = dorpc Fcall::Twalk, :fid => RootFid, :newfid => RootFid, :wname => path + end + + if block_given? + begin + yield self + ensure + cleanup + end + end + return + ensure + @mux.fd.close if @mux + end + + def cleanup + @files.each_value { |f| + f.close + } + ensure + @mux.fd.close + end + + def dorpc(clas, *args) + fcall = clas.new *args + resp = @mux.rpc fcall + if resp.kind_of? Fcall::Rerror + throw Exception, "RPC returned error: #{fcall.class.inspect} #{fcall.fields.inspect}: #{resp.ename}" + end + if fcall.type != resp.type ^ 1 + throw ProtocolException, "Missmatched RPC message types: #{fcall.class.inspect} => #{resp.class.inspect}" + end + resp + end + + def getfid + qsync { + @fids.pop || @lastfid += 1 + } + end + def putfid(fid) + qsync { + if @files.delete(fid) + @fids.push fid + else + throw Exception, "Attempt to free unused fid" + end + } + end + + def open(path, mode = OREAD) + path = path.split('/').reject{|v| v=='' } + fid = getfid + + resp = dorpc Fcall::Twalk, :fid => RootFid, :newfid => fid, :wname => path + file = File.new self, fid, mode + @files[fid] = file + + if block_given? + begin + yield file + ensure + file.close + end + else + file + end + end + + class File + include Stat::Constants + include Client::Constants + include Qlock + def initialize(client, fid, mode) + qlinit + @fid = fid + @client = client + @offset = 0 + @mode = mode + @fd = nil + + fcall = dorpc Fcall::Topen, :mode => mode + @iounit = fcall.iounit + @qid = fcall.qid + end + + def stat + resp = dorpc Fcall::Tstat + resp.stat + end + + def read(count = @iounit, offset = @offset, buf = '') + qsync { + while count > 0 + n = [count, @iounit].min + count -= n + + resp = dorpc Fcall::Tread, :offset => @offset, :count => n + data = resp.data + + @offset += data.length + buf << data + + if data.length < n + break + end + end + } + if buf.length == 0 + nil + else + buf + end + end + def write(data, offset = @offset) + off = 0 + qsync { + while off < data.length + n = [data.length, @iounit].min + + resp = dorpc Fcall::Twrite, :offset => @offset, :data => data[off..off+n-1] + + off += resp.count + @offset += resp.count + if resp.count < n + break + end + end + } + off + end + + def close + fd_close if @fd + ensure + qsync { + dorpc Fcall::Tclunk + } + @client.putfid @fid + + @tg = @fd = @fid = @client = @qid = nil + end + def fd_close + @fd.close + ensure + @tg.list.each { |thr| + thr.kill if thr.alive? + thr.join + } + end + + def to_fd + if @fd + throw Exception, "File descriptor already open for file" + end + + fds = Socket::pair Socket::PF_UNIX, Socket::SOCK_STREAM, 0 + @tg = ThreadGroup.new + mtx = Mutex.new + + reader = nil + writer = nil + mtx.lock + @tg.add reader = Thread.new(fds[1]) { |fd| + mtx.synchronize { + unless (@mode&3) == OWRITE || (@mode&3) == ORDWR + fd.close unless writer.alive? + return + end + } + while data = fd.sysread(@iounit) + if write(data) == 0 + break + end + end + mtx.synchronize { + if fd.eof + writer.terminate + fd.close + elsif !writer.alive? + fd.close + end + } + } + @tg.add writer = Thread.new(fds[1]) { |fd| + mtx.synchronize { + unless (@mode&3) == OREAD || (@mode&3) == ORDWR + fd.close unless reader.alive + return + end + } + len = 1 + while data = read + while data.length > 0 + len = fd.write data + if len == 0 + break + end + data.slice!(0..len-1) + end + if len == 0 + break + end + end + mtx.synchronize { + reader.terminate + fd.close + } + } + mtx.unlock + + @fd = fds[0] + if block_given? + begin + yield @fd + ensure + fd_close + end + else + @fd + end + end + + def dorpc(clas, args = {}) + @client.dorpc clas, args.merge!(:fid => @fid) + end + private :dorpc + end + end +end diff --git a/r9p/fcall.rb b/r9p/fcall.rb @@ -0,0 +1,169 @@ +# Copyright (C) 2007 Kris Maglione +# See PERMISSIONS + +module R9P + class Fcall <Serial + Types = [] + field :size, :dword, :size, 4 + field :type, :byte + field :tag, :word + + def self.type(n) + Types[n] = self + @type = n + end + def self.from(data) + type = data[4] + Types[type].new data + end + + def initialize(data = nil) + fields[:type] = self.class.module_eval { @type } + super(data) + end + + def succ(args = {}) + Types[type+1].new args.merge(:tag => tag) + end + + class Tversion <Fcall + type 100 + field :msize, :dword + field :version, :word, :string + end + class Rversion <Fcall + type 101 + field :msize, :dword + field :version, :word, :string + end + + class Tauth <Fcall + type 102 + field :afid, :dword + field :uname, :word, :string + field :aname, :word, :string + end + class Rauth <Fcall + type 103 + field :aqid, Qid + end + + class Tattach <Fcall + type 104 + field :fid, :dword + field :afid, :dword + field :uname, :word, :string + field :aname, :word, :string + end + class Rattach <Fcall + type 105 + field :qid, Qid + end + + class Terror <Fcall + type 106 + def initialize + throw "Illegal 9P tag 'Terror' encountered" + end + end + class Rerror <Fcall + type 107 + field :ename, :word, :string + end + + class Tflush <Fcall + type 108 + field :oldtag, :word + end + class Rflush <Fcall + type 109 + end + + class Twalk <Fcall + type 110 + field :fid, :dword + field :newfid, :dword + field :wname, :word, [ :word, :string ] + end + class Rwalk <Fcall + type 111 + field :wqid, :word, [ Qid ] + end + + class Topen <Fcall + type 112 + field :fid, :dword + field :mode, :byte + end + class Ropen <Fcall + type 113 + field :qid, Qid + field :iounit, :dword + end + + class Tcreate <Fcall + type 114 + field :fid, :dword + field :name, :word, :string + field :perm, :dword + field :mode, :byte + end + class Rcreate <Fcall + type 115 + field :qid, Qid + field :iounit, :dword + end + + class Tread <Fcall + type 116 + field :fid, :dword + field :offset, :qword + field :count, :dword + end + class Rread <Fcall + type 117 + field :data, :dword, :string + end + + class Twrite <Fcall + type 118 + field :fid, :dword + field :offset, :qword + field :data, :dword, :string + end + class Rwrite <Rread + type 119 + end + + class Tclunk <Fcall + type 120 + field :fid, :dword + end + class Rclunk <Fcall + type 121 + end + + class Tremove <Tclunk + type 122 + end + class Rremove <Fcall + type 123 + end + + class Tstat <Tclunk + type 124 + end + class Rstat <Fcall + type 125 + field :sstat, :word, :size, 2 + field :stat, Stat + end + + class Twstat <Rstat + type 126 + end + class Rwstat <Fcall + type 127 + end + end +end diff --git a/r9p/ixp.rb b/r9p/ixp.rb diff --git a/r9p/mux.rb b/r9p/mux.rb @@ -0,0 +1,239 @@ +# Derived from libmux, available in Plan 9 under /sys/src/libmux +# under the following terms: +# +# Copyright (C) 2003-2006 Russ Cox, Massachusetts Institute of Technology +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +require 'socket' + +module R9P + class Mux + include Qlock + include Rendez + + attr_reader :fd + + def initialize(con, opts = {}, &block) + qlinit + @queue = [] + @lock = self + @outlock = Mutex.new + @inlock = Mutex.new + @deserialize = block + @freetag = 0 + @wait = [] + @mwait = 0 + @nwait = 0 + @mintag = opts[:mintag] || 0 + @maxtag = opts[:maxtag] || 1<<16-1 + + case con + when IO + @fd = con + when String + spec = con.split '!' + case spec.shift + when 'unix' + print "#{self.class.inspect}: init UNIXSocket: #{spec[0]}\n" if $debug > 1 + @fd = UNIXSocket.new spec[0] + when 'tcp' + @fd = TCPSocket.new *spec + else + throw "Invalid connection spec" + end + else + throw "Invalid connection spec" + end + if @fd == nil + throw "No connection" + end + end + + def rpc(dat) + r = newrpc dat + + begin + qlock + while @muxer && @muxer != r && r.data == nil + r.sleep + end + + if r.data == nil + if @muxer && @muxer != r + fail + end + @muxer = r + qunlock + begin + while r.data == nil + data = recv + if data == nil + qlock + @queue.delete r + throw "unexpected eof" + end + dispatch data + end + qlock + ensure + electmuxer + end + end + + ensure + puttag(r) + qunlock + end + r.data + end + + def electmuxer + @queue.each { |rpc| + if @muxer != rpc && rpc.async == false + @muxer = rpc + rpc.wakeup + return + end + } + @muxer = nil + end + + def dispatch(dat) + tag = dat.tag - @mintag + r = nil + qsync { + if tag < 0 || tag > @mwait + printf "#{$0}: bad rpc tag: %ux\n", dat.tag + return + end + r = @wait[tag] + if r == nil || @queue.delete(r) == nil + printf "#{$0}: bad rpc tag: %ux (noone waiting on it)\n", dat.tag + return + end + } + r.data = dat + r.wakeup + end + + def gettag(r) + tag = 0 + + while @nwait == @mwait + if @mwait < @maxtag - @mintag + mw = if @mwait == 0 + 1 + else + @mwait << 1 + end + @wait[mw] = nil + @freetag = @mwait + @mwait = mw + break + end + sleep + end + + tag = @freetag + if @wait[tag] + tag.upto(@mwait) { |n| + if @wait[n] == nil + tag = n + break + end + } + 0.upto(@freetag) { |n| + if @wait[n] == nil + tag = n + break + end + } + end + + if @wait[tag] + throw "nwait botch" + end + + @nwait += 1 + @wait[tag] = r + r.tag = tag + @mintag + r.data.tag = r.tag + r.data = nil + r.tag + end + def puttag(r) + t = r.tag + if @wait[t] != r + fail + end + @wait[t] = nil + @nwait -= 1 + @freetag = t + wakeup + end + + def send(dat) + dat.serialize + len = @fd.syswrite dat.serial + print "send(#{dat.class.inspect}): len=#{len} data=#{dat.serial.inspect}" if $debug > 1 + + if len < dat.serial.length + return false + end + true + end + def recv + data = @fd.read 4 + if data + len, = data.unpack "V" + data << @fd.read(len-4) + print "#{self.class.inspect}: recv: data=#{data.inspect}" if $debug > 1 + @deserialize.call data + end + end + + def newrpc(dat) + rpc = Rpc.new self + tag = nil + + rpc.data = dat + qsync { + gettag rpc + @queue << rpc + } + + if rpc.tag < 0 || send(dat) == false + qsync { + @queue.delete rpc + puttag rpc + } + return nil + end + + rpc + end + private :gettag, :puttag,:send, :recv, :newrpc, :electmuxer, :dispatch + + class Rpc + include Rendez + attr_accessor :data, :waiting, :async, :tag + + def initialize(mux) + @lock = mux + @mux = mux + @data = nil + @waiting = true + @async = false + end + end + end +end diff --git a/r9p/qid.rb b/r9p/qid.rb @@ -0,0 +1,45 @@ +# Copyright (C) 2007 Kris Maglione +# See PERMISSIONS + +module R9P + class Qid <Serial + module Constants + QTFILE = 0x00 + QTLINK = 0x01 + QTSYMLINK = 0x02 + QTTMP = 0x04 + QTAUTH = 0x08 + QTMOUNT = 0x10 + QTEXCL = 0x20 + QTAPPEND = 0x40 + QTDIR = 0x80 + end + include Constants + + class Type <Serial + field :type, :byte + + alias to_i type + + def to_s + const = Qid.constants.grep(/^QT/) + const.collect! { |c| + v = Qid.const_get(c) + if type&v == v + c + end + } + const.reject! {|i| i == nil} + const.join '|' + end + end + + field :type, Type + field :vers, :dword + field :path, :qword + + def to_s + "type: #{type} vers: #{vers} path: #{path}" + end + end +end diff --git a/r9p/serial.rb b/r9p/serial.rb @@ -0,0 +1,181 @@ +# Copyright (C) 2007 Kris Maglione +# See PERMISSIONS + +module R9P + class Serial + Conv = { + :byte => [ 1, 'C' ], + :word => [ 2, 'v' ], + :dword => [ 4, 'V' ], + :qid => [ 13, 'CVVV' ], + } + + attr_reader :fields, :serial, :opstack + + def initialize(data) + @fields ||= {} + @serial ||= '' + @opstack = [] + print "#{self.class.inspect}.new(#{data.inspect})\n" if $debug > 5 + case data + when String + deserialize data + when Hash + fields.merge! data + end + print "\tFields: #{fields.inspect}\n\n" if $debug > 5 + end + + def deserialize(data = @serial) + @opstack = [] + @serial = data + unstack self.class.field_stack + self + end + def serialize + print "serialize #{self.inspect}\n" if $debug > 4 + @opstack = [] + @serial = stack self.class.field_stack + end + + def fields + @fields ||= {} + end + def opstack + @opstack + end + def serial + @serial ||= "" + end + + def slice + if @offset > 0 + @serial.slice!(0..@offset-1) + @offset = 0 + end + end + def unstack(stack) + print "\nunstack(stack): #{self.class.inspect}\n" if $debug > 4 + @offset = 0 + stack.each { |op| + print "\top: #{op.inspect} top: #{@opstack[-1].inspect}\n" if $debug > 4 + print "\tserial: #{@serial.inspect}\n" if $debug > 6 + case op + when Field + fields[op.name] = pop + when Array + list = (1..pop).to_a.collect { + unstack op + pop + } + push list + when :string + num = pop + str, = unpack num, "a#{num}" + push str + when :qword + low, high = unpack 8, "VV" + push high<<32|low + when :size + when Symbol + push unpack(*Conv[op])[0] + when Module + if op <= Serial + slice + push op.new(@serial) + end + end + } + print "\n/unstack(stack): #{self.class.inspect}\n" if $debug > 4 + slice + self + end + def stack(stack) + print "\nstack(stack): #{self.class.inspect}\n" if $debug > 4 + stack.reverse.each { |op| + print "\top: #{op.inspect} top: #{@opstack[-1].inspect}\n" if $debug > 4 + print "\tstack: #{@opstack.inspect}\n" if $debug > 5 + print "\tserial: #{@serial.inspect}\n" if $debug > 5 + case op + when Field + push @fields[op.name] + when Array + list = pop + list.reverse.each { |i| + push i + stack op + } + push list.length + when :string + str = pop + push str + push str.length + when :qword + num = pop + push [num, num >> 32].pack("VV") + when :size + add = pop + push @opstack.inject(add) { |l, o| l + o.length } + when Symbol + push [pop].pack(Conv[op][1]) + when Module + if op <= Serial + push op.new(@serial).serialize + end + else + pop + push op + end + } + print "\n/stack(stack): #{self.class.inspect}\n" if $debug > 4 + @opstack.reverse.join '' + end + + def push(arg) + @opstack.push arg + end + def pop + @opstack.pop + end + def unpack(length, fmt) + print "unpack(#{length}, #{fmt}) @offset: #{@offset}\n" if $debug > 5 + @offset += length + @serial.unpack("@#{@offset-length}#{fmt}") + end + + def to_s + "fields: #{@fields.inspect}, serial: #{@serial.inspect}, stack: #{@opstack.inspect}" + end + def inspect + "#<#{self.class.inspect} #{to_s}>" + end + + class Field + attr_reader :name + def initialize(name) + @name = name + end + end + + class <<self + def field_stack + @field_stack ||=if self.superclass <= Serial + self.superclass.field_stack.clone + else + [] + end + end + def field(name, *rest) + module_eval <<-"end_eval" + def #{name.id2name} + @fields[#{name.inspect}] + end + def #{name.id2name}=(val) + @fields[#{name.inspect}] = val + end + end_eval + self.field_stack.concat(rest) << Field.new(name) + end + end + end +end diff --git a/r9p/stat.rb b/r9p/stat.rb @@ -0,0 +1,63 @@ +# Copyright (C) 2007 Kris Maglione +# See PERMISSIONS + +module R9P + class Stat <Serial + module Constants + DMDIR =0x80000000 + DMAPPEND =0x40000000 + DMEXCL =0x20000000 + DMMOUNT =0x10000000 + DMAUTH =0x08000000 + DMTMP =0x04000000 + DMSYMLINK =0x02000000 + DMDEVICE =0x00800000 + DMNAMEDPIPE =0x00200000 + DMSOCKET =0x00100000 + DMSETUID =0x00080000 + DMSETGID =0x00040000 + end + include Constants + + class Mode <Serial + field :mode, :dword + alias_method :to_i, :mode + def to_s + modes = 'rwxrwxrwx' + 0.upto(8) { |i| + if mode&(1 << i) == 0 + modes[-i-1] = '-' + end + } + if mode&Stat::DMDIR > 0 + 'd' + elsif mode&Stat::DMAPPEND > 0 + 'a' + else + '-' + end + modes + end + end + + field :size, :word, :size, 2 + field :type, :word + field :dev, :dword + field :qid, Qid + field :mode, Mode + field :atime, :dword + field :atime, :dword + field :length, :qword + field :name, :word, :string + field :uid, :word, :string + field :gid, :word, :string + field :muid, :word, :string + + def self.from_data(data) + list = [] + while data.length > 0 + list << new(data) + end + list + end + end +end diff --git a/r9p/test.rb b/r9p/test.rb @@ -0,0 +1,65 @@ +require 'r9p.rb' + +module R9P + include Client::Constants + + Client.new('unix!/tmp/ns.kris.:1/wmii') { |c| + c.open("event", OWRITE) { |f| + f.to_fd { |fd| + fd.print "Foo!\n" + } + } + c.open("keys") { |f| + print f.read + } + } +end + +=begin + +include Client::Constants + +$pipe = UNIXSocket.new "/tmp/ns.kris.:1/wmii" +#$pipe = IO.for_fd(0, "r+") + +def dorpc(fcall) + p fcall + fcall.serialize + $pipe.write(fcall.serial) + + data = $pipe.read(4) + size, = data.unpack("V") + data << $pipe.read(size-data.length) + + fcall = Fcall.from(data) + p fcall + print "\n\n" + fcall +end + +$debug = 0 + +rversion =dorpc Fcall::Tversion.new(:tag => 1, :msize => 8192, :version => "9P2000") +dorpc Fcall::Tattach.new(:tag => 1, :fid => 1, :afid => ~0, :uname => "kris", :aname => "kris") +dorpc Fcall::Twalk.new(:tag => 1, :fid => 1, :newfid => 3, :wname => [ "ctl" ]) +dorpc Fcall::Tstat.new(:tag => 1, :fid => 3, :offset => 0, :count => 500) +dorpc Fcall::Topen.new(:tag => 1, :fid => 3, :mode => OREAD) + +off = 0 +begin + fcall = dorpc Fcall::Tread.new(:tag => 1, :fid => 3, :offset => off, :count => rversion.msize) + print fcall.data + off += fcall.data.length +end while fcall.data.length > 0 + +print "Done.\n" + +exit + +fcall = dorpc Fcall::Tread.new(:tag => 1, :fid => 3, :offset => 0, :count => 500) +print "Stats: " +Stat.from_data(fcall.data).each do |i| + i.fields.each_pair { |i, j| print "#{i} => #{j}\n" } + print "\n" +end +=end diff --git a/r9p/thread.rb b/r9p/thread.rb @@ -0,0 +1,117 @@ +# Copyright (C) 2007 Kris Maglione +# See PERMISSIONS + +begin + require 'thread' +rescue + # Threading is not necessary for most functions + module Stub + class <<self + def nops(*arg) + arg.each { |s| + module_eval <<-"end_eval" + alias #{s.id2name} nop + end_eval + } + end + def error(name, *arg) + first = arg.shift + module_eval <<-"end_eval" + def #{first.id2name}(*arg) + throw #{name.inspect} + end + end_eval + arg.each { |s| module_eval "alias #{s.id2name} #{first.id2name}" } + end + end + def nop(*arg) + puts "In nop!" + if block_given? + yield + end + end + end + class Thread + include Stub + @@dummy = new + def current + @@dummy + end + Stub.error 'Threading not implemented', :initialize, :wakeup + Stub.nops :exclusive, :critical, :critical= + + class <<self + Stub.error 'Threading not implemented', :fork, :start, :stop + end + end + + # Locking is not necessary without threading + class Mutex + include Stub + Stub.nops :lock, :unlock, :synchronize, :initialize, :locked? + def locked? + false + end + end +end + +module R9P + module Qlock + def qlinit + @ql_queue = [] + @ql_locked = nil + end + + def qlock + cr = Thread.critical + while (Thread.critical = true; @ql_locked != nil && @ql_locked != Thread.current) + unless @ql_queue.include?(Thread.current) + @ql_queue.push Thread.current + end + Thread.stop + end + @ql_locked = Thread.current + Thread.critical = cr + self + end + def qunlock + if @ql_locked != Thread.current + raise + end + cr = Thread.critical + Thread.critical = true + thr = @ql_locked = @ql_queue.shift + @ql_locked.wakeup if @ql_locked + if block_given? + yield + end + ensure + Thread.critical = cr + thr.run if thr + end + def qsync + qlock + yield + ensure + qunlock + end + def qlocked? + @ql_locked ? true : false + end + end + module Rendez + def sleep + @thread = Thread.current + @lock.qunlock { + Thread.stop + } + @lock.qlock + @thread = nil if @thread == Thread.current + end + def wakeup + if @thread + @thread.wakeup + end + end + end +end