[ruby-dev:10267] FW: ruby's select causes busy waiting?
From:
Jun-ichiro itojun Hagino <itojun@...>
Date:
2000-07-09 13:28:02 UTC
List:
ruby-dev #10267
i'm forwading it as ruby-dev implement from: based filters.
itojun
------- Forwarded Message
Return-Path: jinmei@isl.rdc.toshiba.co.jp
Return-Path: <jinmei@isl.rdc.toshiba.co.jp>
Received: from orange.kame.net (orange.kame.net [203.178.141.194])
by coconut.itojun.org (8.9.3+3.2W/3.7W) with ESMTP id WAA03552
for <itojun@itojun.org>; Sun, 9 Jul 2000 22:22:50 +0900 (JST)
Received: from shuttle.wide.toshiba.co.jp (shuttle.wide.toshiba.co.jp [202.249.10.124])
by orange.kame.net (8.9.3+3.2W/3.7W/smtpfeed 1.06) with ESMTP id WAA14145
for <core@kame.net>; Sun, 9 Jul 2000 22:22:48 +0900 (JST)
Received: from localhost (shuttle.sixyards.wide.toshiba.co.jp [3ffe:501:100f:0:200:f8ff:fe01:61cf])
by shuttle.wide.toshiba.co.jp (8.9.1+3.1W/8.9.1) with ESMTP id WAA07954;
Sun, 9 Jul 2000 22:07:44 +0900 (JST)
Date: Sun, 09 Jul 2000 22:18:58 +0900
Message-ID: <y7v1z13phkt.wl@condor.isl.rdc.toshiba.co.jp>
From: JINMEI Tatuya / 神明達哉 <jinmei@isl.rdc.toshiba.co.jp>
To: ruby-dev@netlab.co.jp
Cc: core@kame.net
Subject: ruby's select causes busy waiting?
User-Agent: Wanderlust/2.3.0 (Roam) Emacs/20.6 Mule/4.0 (HANANOEN)
Organization: Research & Development Center, Toshiba Corp., Kawasaki, Japan.
MIME-Version: 1.0 (generated by SEMI 1.13.7 - "Awazu")
Content-Type: multipart/mixed;
boundary="Multipart_Sun_Jul__9_22:18:57_2000-1"
X-Dispatcher: imput version 980905(IM100)
Lines: 492
X-Saved: Sun, Jul 9 22:23:03 JST 2000
X-Saved: kame folder in itojun
X-Filter: mailagent [version 3.0 PL68] for itojun@itojun.org
- --Multipart_Sun_Jul__9_22:18:57_2000-1
Content-Type: text/plain; charset=ISO-2022-JP
東芝の神明と申します。
KAMEプロジェクトが配布しているclient/server型ruby applicationのdtcp
(http://www.kame.net/dev/cvsweb.cgi/kame/kame/kame/dtcp/)
というツールを、
- - KAME対応したFreeBSD 3.4(KAMEのバージョンは2000年6月頭くらい)
- - ruby 1.4.4
という環境で使っているのですが、どうもserver側でclientの接続を受け付け
ている間、rubyがbusy waitしているのではないかと思われるようなふしがあ
ります。システムのloadがすごく高くなって、dtcp server processのstatus
がずーっとRUNのままになっているからです。
一瞬無限ループしてるのかなとも思ったのですが、clientが接続を切るとload
が下がってselect状態になるので、厳密な意味での無限ループではなさそうで
す。
KAMEプロジェクトのitojunさんいわく、timeout付きselectが(ちゃんと
timeoutせずに)すぐに抜けているとかいったあたりが原因ではないかという話
なのですが、いかがでしょうか?
rubyの内部状態まで追えていないので漠然としたレポートですみません。とり
あえずご連絡まで。
なお、問題のserverについては、大したサイズではないのでこのメールに添付
しておきます。
神明 達哉 (jinmei@isl.rdc.toshiba.co.jp)
株式会社 東芝 研究開発センター
通信プラットホームラボラトリー
p.s. 重ね重ねすみません、僕はruby-dev@netlab.co.jpには所属していません
ので、もしお返事をいただける場合は直接ccしていただけると幸いです。もし
「質問やレポートはMLに入ってから」ということでしたら、ご指摘いただけれ
ばそのように致します。よろしくお願いします。
- --Multipart_Sun_Jul__9_22:18:57_2000-1
Content-Type: text/plain; charset=US-ASCII
Content-Disposition: attachment; filename="dtcps.rb"
Content-Transfer-Encoding: 7bit
#! @PREFIX@/ruby
#
# dtcpd, Turmpet Dynamic Tunel Configuration Protocol daemon
#
#
# Copyright (C) 1999 WIDE Project.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the project nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Id: dtcps.rb,v 1.3 2000/04/21 14:21:21 jinmei Exp $
#
require "socket"
require "thread"
require "md5"
require "dbm"
require "etc"
# XXX should be derived from system headers
IPPROTO_IPV6 = 41
IPV6_FAITH = 29
TUNIF = "gif"
AUTHTIMEOUT = 60
TUNTIMEOUT = 300
# must be less than 10, against RIPng and PIM6 - useless
TRAFFICTIMEOUT = 0
POPAUTHUID = 'pop'
DEBUG = false
def daemon(nochdir, noclose)
pid = fork
if pid == -1
return -1
elsif pid != nil
exit 0
end
Process.setsid()
Dir.chdir('/') if (nochdir == 0)
if noclose == 0
devnull = open("/dev/null", "r+")
$stdin.reopen(devnull)
$stdout.reopen(devnull)
p = IO::pipe
pid = fork
if pid == -1
$stderr.reopen(devnull)
elsif pid == nil
p[1].close
STDIN.reopen(p[0])
p[0].close
exec("logger -t #{File.basename($0)} -p daemon.notice")
else
p[0].close
$stderr.reopen(p[1])
p[1].close
end
end
return 0
end
def logmsg(msg)
$stderr.print msg
end
def debugmsg(msg)
$stderr.print msg if ($debug)
end
# TODO: check for duplicated tunnel configuration
def gettunnel(me, her)
tmpfile = "/tmp/gettunnel#{$$}.#{me}-#{her}"
system("ifconfig -a |grep ^#{TUNIF} | grep -v UP > #{tmpfile}")
f = open(tmpfile, "r")
s = f.readline
f.close
File::unlink(tmpfile)
s = s[0, s.index(':')]
debugmsg("gifconfig #{s} #{me} #{her}\n")
system("gifconfig #{s} #{me} #{her}")
debugmsg("ifconfig #{s} up\n")
system("ifconfig #{s} up")
return s
end
def tunnelsetup(s, user, type)
me = s.addr()[3]
her = s.peeraddr()[3]
debugmsg("#{s}: tunnel #{me} -> #{her}\n")
case type
when 'host'
if $prefix == nil
debugmsg("#{s}: tunnel type #{type} not supported\n")
return nil, "unsupported tunnel type #{type}"
end
tunif = gettunnel(me, her)
if (tunif == nil)
debugmsg("#{s}: tunnel interface sold out\n")
return nil, 'tunnel interface sold out'
end
debugmsg("#{s}: tunnel interface #{tunif}\n")
if tunif =~ /(\d+)$/
tunid = $1.to_i
heraddr = sprintf("%s%04x", $prefix, (tunid + 1) * 4 + 2)
myaddr = sprintf("%s%04x", $prefix, (tunid + 1) * 4 + 1)
debugmsg("ifconfig #{tunif} inet6 #{myaddr} #{heraddr} prefixlen 126 alias\n")
system("ifconfig #{tunif} inet6 #{myaddr} #{heraddr} prefixlen 126 alias")
x = [tunif, her, me, heraddr, myaddr]
err = nil
else
tunnelcleanup([tunif, her, me])
x = nil
err = 'internal error: tunnel interface name format is wrong'
end
when 'tunnelonly'
tunif = gettunnel(me, her)
if (tunif == nil)
debugmsg("#{s}: could not configure tunnel\n")
return nil, 'tunnel interface sold out'
end
debugmsg("#{s}: tunnel interface #{tunif}\n")
x = [tunif, her, me]
err = nil
else
debugmsg("#{s}: unsupported tunnel type #{type}\n")
err = "unsupported tunnel type #{type}"
x = nil
end
return x, err
end
def getipkts(intface)
tmpfile = "/tmp/getipkts#{$$}.#{intface}"
system("netstat -in -I #{intface} > #{tmpfile}")
f = open(tmpfile, "r")
s = f.readline
s = f.readline
f.close
File::unlink(tmpfile)
t = s.split(/[ \t]+/)
if t.length < 5
debugmsg("#{intface} ipkts unknown, returning -1\n")
end
debugmsg("#{intface} ipkts = #{t[t.length - 5]}\n")
return t[t.length - 5]
end
def checktraffic(tun)
return if TRAFFICTIMEOUT == 0
ipkts = getipkts(tun[0])
while TRUE
sleep TRAFFICTIMEOUT
i = getipkts(tun[0])
next if i == -1
break if ipkts >= i
ipkts = i
end
end
def tunnelcleanup(tun)
logmsg("#{tun[0]} disconnected\n")
if tun.length == 5
debugmsg("ifconfig #{tun[0]} inet6 #{tun[4]} #{tun[3]} -alias\n")
system("ifconfig #{tun[0]} inet6 #{tun[4]} #{tun[3]} -alias")
end
debugmsg("ifconfig #{tun[0]} down\n")
system("ifconfig #{tun[0]} down")
end
def service_dtcp(sock, name)
debugmsg("service_dtcp(#{sock}, #{name})\n")
while TRUE
debugmsg("service_dtcp(#{sock}, #{name}) accepting\n")
sa = sock.accept
debugmsg("service_dtcp(#{sock}, #{name}) accepted #{sa}\n")
Thread.start {
threads = []
debugmsg("accepted #{sa} -> #{Thread.current}\n")
s = sa
tun = []
# send challenge
challenge = seed()
s.print "+OK #{challenge} KAME tunnel server\r\n"
# check response
# tunnel itojun RESPONSE type
while TRUE
t = select([s], [], [s], tun == [] ? AUTHTIMEOUT : TUNTIMEOUT)
if t == nil
s.print "-ERR connection timed out, disconnecting\r\n"
break
end
if s.eof?
break
end
response = s.readline
response.gsub!(/[\n\r]/, '')
if response != ''
t = response.split(/ /)
t[0].tr!('A-Z', 'a-z')
else
t = ['']
end
debugmsg("#{s}: got <#{response}>\n")
case t[0]
when 'tunnel'
if (t.length != 4)
debugmsg("client #{s} sent wrong #{t[0]} command\n")
debugmsg("#{s}: sent <-ERR authentication failed.>\n")
s.print "-ERR authentication failed.\r\n"
next
end
user = t[1]
type = t[3]
pass = getpopauth(user)
if pass == nil
logmsg("client #{s} has no password in database for #{user}\n")
debugmsg("#{s}: sent <-ERR authentication failed.>\n")
s.print "-ERR authentication failed.\r\n"
next
end
# get password from the username
# $stderr.print "authenticate(#{user} #{challenge} #{pass}): "
# debugmsg(authenticate(user, challenge, pass) + "\n")
# debugmsg("target: #{t[2]}\n")
if (authenticate(user, challenge, pass) == t[2])
debugmsg("client #{s.addr()[3]} on #{s}\n")
logmsg("client #{s.addr()[3]} authenticated as #{user}\n")
auth = true
tun, err = tunnelsetup(s, user, type)
if tun == nil
logmsg("failed to configure for #{user} type #{type}: #{err}\n")
debugmsg("#{s}: sent <-ERR #{err}>\n")
s.print "-ERR #{err}\r\n"
else
t = tun[1, tun.length - 1]
logmsg("#{tun[0]} configured for #{user} type #{type}: " + t.join(' ') + "\n")
debugmsg("#{s}: sent <+OK #{t.join(' ')}>\n")
s.print "+OK ", t.join(' '), "\r\n"
end
else
logmsg("client #{s} not authenticated\n")
debugmsg("#{s}: sent <-ERR authentication failed.>\n")
s.print "-ERR authentication failed.\r\n"
end
when 'ping'
debugmsg("#{s}: sent <+OK hi, happy to hear from you>\n")
s.print "+OK hi, happy to hear from you\r\n"
when 'help'
debugmsg("#{s}: sent <+OK valid commands are: TUNNEL PING QUIT>\n")
s.print "+OK valid commands are: TUNNEL PING QUIT\r\n"
when 'quit'
debugmsg("#{s}: sent <+OK see you soon.>\n")
s.print "+OK see you soon.\r\n"
break
else
debugmsg("client #{s} sent invalid command #{t[0]}\n")
debugmsg("#{s}: sent <-ERR invalid command>\n")
s.print "-ERR invalid command\r\n"
end
end
if tun != []
checktraffic(tun)
tunnelcleanup(tun)
end
s.flush
s.shutdown(1)
debugmsg("shutdown #{s} #{Thread.current}\n")
}
end
debugmsg("service_dtcp(#{sock}, #{name}) finished\n")
end
def usage()
$stderr.print "usage: #{File.basename($0)} [-dD] [-p port] [prefix]\n"
end
def seed()
m = MD5.new(Time.now.to_s)
m.update($$.to_s)
m.update(Socket.gethostname())
return m.digest.unpack("H32")[0].tr('a-f', 'A-F')
end
def authenticate(user, seed, pass)
m = MD5.new(user)
m.update(seed)
m.update(pass)
return m.digest.unpack("H32")[0].tr('a-f', 'A-F')
end
# NOTE: strings are terminated by "\0"...
def getpopauth(user)
pw = Etc.getpwnam(POPAUTHUID)
if pw == nil
debugmsg("no user named pop\n")
return nil
end
origuid = Process.euid
# XXX begin seteuid(pop)
Process.euid = pw[2]
f = DBM.open('/usr/local/etc/popper/pop.auth', nil)
if f == nil
debugmsg("no password database found\n")
Process.euid = origuid
return nil
end
p = f[user + "\0"]
f.close
Process.euid = origuid
# XXX end seteuid(pop)
if p == nil
debugmsg("no relevant password database item found\n")
return nil
end
if p[p.length - 1] == 0
p = p[0, p.length - 1]
end
for i in 0 .. p.length - 1
p[i] = [p[i] ^ 0xff].pack('C')
end
debugmsg("ok, relevant password database item found\n")
return p
end
#------------------------------------------------------------
port = 20200
$prefix = nil
$daemonize = true
$debug = DEBUG
while ARGV[0] =~ /^-/ do
case ARGV[0]
when /-p/
ARGV.shift
port = ARGV[0]
when /-d/
$debug = !$debug
when /-D/
$daemonize = !$daemonize
else
usage()
exit 0
end
ARGV.shift
end
case ARGV.length
when 0
$prefix = nil
when 1
$prefix = ARGV[0]
if $prefix !~ /^[0-9a-fA-f:]*::$/
usage()
exit 1
end
else
usage()
exit 1
end
res = []
t = Socket.getaddrinfo(nil, port, Socket::PF_INET, Socket::SOCK_STREAM,
nil, Socket::AI_PASSIVE)
if (t.size <= 0)
$stderr.print "FATAL: getaddrinfo failed (port=#{port})\n"
exit 1
end
res += t
if $daemonize
daemon(0, 0)
end
sockpool = []
names = []
listenthreads = []
res.each do |i|
s = TCPserver.new(i[3], i[1])
n = Socket.getnameinfo(s.getsockname, Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV).join(" port ")
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
sockpool.push s
names.push n
end
if $debug
(0 .. sockpool.size - 1).each do |i|
debugmsg("listen[#{i}]: #{sockpool[i]} #{names[i]}\n")
end
end
(0 .. sockpool.size - 1).each do |i|
listenthreads[i] = Thread.start {
debugmsg("listen[#{i}]: thread #{Thread.current}\n")
service_dtcp(sockpool[i], names[i])
}
end
for i in listenthreads
if VERSION =~ /^1\.2/
Thread.join(i)
else
i.join
end
end
exit 0
- --Multipart_Sun_Jul__9_22:18:57_2000-1
Content-Type: text/plain; charset=US-ASCII
- --Multipart_Sun_Jul__9_22:18:57_2000-1--
------- End of Forwarded Message