progress on mung server

--HG--
branch : mung
This commit is contained in:
cecilkorik 2010-11-13 07:55:35 +00:00
parent 8a5b25e6b0
commit d3f845144e
11 changed files with 826 additions and 18 deletions

View file

@ -3,7 +3,7 @@
CALLBUILTIN eval argstr
.
:"handle_connection" x *
RETURN #0;
RETURN #0
.

104
builtins.py Executable file
View file

@ -0,0 +1,104 @@
builtin_map = {}
# decorator for builtin function with "n" args
def bi(func, n):
def builtin_varg(vm):
args = vm.pop(n)
rv = func(args)
vm.push(rv)
return builtin_varg
"""
# manage properties
builtin_map.update({
'properties': bi_properties,
'add_property': bi_add_property,
'delete_property': bi_delete_property,
'clear_property': bi_clear_property,
'set_property_opts': bi_set_property_opts,
'get_property_opts': bi_get_property_opts,
'set_property_owner': bi_set_property_owner,
'get_property_owner': bi_get_property_owner,
})
# manage files
builtin_map.update({
'files': bi_files,
'add_file': bi_add_file,
'delete_file': bi_delete_file,
'set_file_opts': bi_set_file_opts,
'get_file_opts': bi_get_file_opts,
'set_file_owner': bi_set_file_owner,
'get_file_owner': bi_get_file_owner,
})
# manage functions
builtin_map.update({
'functions': bi_functions,
'add_function': bi_add_function,
'delete_function': bi_delete_function,
'set_function_code': bi_set_function_code,
'get_function_code': bi_get_function_code,
'set_function_opts': bi_set_function_opts,
'get_function_opts': bi_get_function_opts,
'set_function_args': bi_set_function_args,
'get_function_args': bi_get_function_args,
'set_function_owner': bi_set_function_owner,
'get_function_owner': bi_get_function_owner,
})
# server configuration
builtin_map.update({
'set_server_var': bi_set_server_var,
'get_server_var': bi_get_server_var,
'save_database': bi_save_database,
'shutdown': bi_shutdown,
'server_info': bi_server_info,
'time': bi_time,
'format_time': bi_format_time,
})
# manage running tasks
builtin_map.update({
'set_perms': bi_set_perms,
'get_perms': bi_get_perms,
'task_id': bi_task_id,
'kill_task': bi_kill_task,
'eval': bi_eval,
'fork': bi_fork,
'sleep': bi_sleep,
'resume': bi_resume,
'raise': bi_raise,
'stack': bi_stack,
})
# manage objects
builtin_map.update({
'create': bi_create,
'destroy': bi_destroy,
'set_parent': bi_set_parent,
'parent': bi_parent,
'children': bi_children,
'set_owner': bi_set_owner,
'owner': bi_owner,
'max_object': bi_max_object,
'renumber': bi_renumber,
})
# manage connections and ports
builtin_map.update({
'connect': bi_connect,
'disconnect': bi_disconnect,
'open_connection': bi_open_connection,
'close_connection': bi_close_connection,
'incoming_connections': bi_incoming_connections,
'outgoing_connections': bi_outgoing_connections,
'get_connection_info': bi_get_connection_info,
'set_connection_opts': bi_set_connection_opts,
'send': bi_send,
'recv': bi_recv,
'listen': bi_listen,
'unlisten': bi_unlisten,
'setuid': bi_setuid,
})
"""

View file

@ -1,3 +1,11 @@
class DSDB_Database(object):
def __init__(self, dbname):
self.dbname = dbname
self.fn = "%s.dsdb" % (dbname,)
self.load_from_file()
def load_from_file(self):
fd = open(self.fn, 'rb')
fd

16
database_fake.py Executable file
View file

@ -0,0 +1,16 @@
class Fake_Database(object):
def __init__(self, dbname):
pass
def set_property(self, obj, prop, val):
pass
def set_file(self, obj, fn, val):
pass
def get_property(self, obj, prop):
pass
def get_file(self, obj, fn):
pass

149
ebnf.py Executable file
View file

@ -0,0 +1,149 @@
# This module tries to implement ISO 14977 standard with pyparsing.
# pyparsing version 1.1 or greater is required.
# ISO 14977 standardize The Extended Backus-Naur Form(EBNF) syntax.
# You can read a final draft version here:
# http://www.cl.cam.ac.uk/~mgk25/iso-ebnf.html
from pyparsing import *
all_names = '''
integer
meta_identifier
terminal_string
optional_sequence
repeated_sequence
grouped_sequence
syntactic_primary
syntactic_factor
syntactic_term
single_definition
definitions_list
syntax_rule
syntax
'''.split()
integer = Word(nums)
meta_identifier = Word(alphas, alphanums + '_')
terminal_string = Suppress("'") + CharsNotIn("'") + Suppress("'") ^ \
Suppress('"') + CharsNotIn('"') + Suppress('"')
definitions_list = Forward()
optional_sequence = Suppress('[') + definitions_list + Suppress(']')
repeated_sequence = Suppress('{') + definitions_list + Suppress('}')
grouped_sequence = Suppress('(') + definitions_list + Suppress(')')
syntactic_primary = optional_sequence ^ repeated_sequence ^ \
grouped_sequence ^ meta_identifier ^ terminal_string
syntactic_factor = Optional(integer + Suppress('*')) + syntactic_primary
syntactic_term = syntactic_factor + Optional(Suppress('-') + syntactic_factor)
single_definition = delimitedList(syntactic_term, ',')
definitions_list << delimitedList(single_definition, '|')
syntax_rule = meta_identifier + Suppress('=') + definitions_list + \
Suppress(';')
ebnfComment = ( "(*" +
ZeroOrMore( CharsNotIn("*") | ( "*" + ~Literal(")") ) ) +
"*)" ).streamline().setName("ebnfComment")
syntax = OneOrMore(syntax_rule)
syntax.ignore(ebnfComment)
def do_integer(str, loc, toks):
return int(toks[0])
def do_meta_identifier(str, loc, toks):
if toks[0] in symbol_table:
return symbol_table[toks[0]]
else:
forward_count.value += 1
symbol_table[toks[0]] = Forward()
return symbol_table[toks[0]]
def do_terminal_string(str, loc, toks):
return Literal(toks[0])
def do_optional_sequence(str, loc, toks):
return Optional(toks[0])
def do_repeated_sequence(str, loc, toks):
return ZeroOrMore(toks[0])
def do_grouped_sequence(str, loc, toks):
return Group(toks[0])
def do_syntactic_primary(str, loc, toks):
return toks[0]
def do_syntactic_factor(str, loc, toks):
if len(toks) == 2:
# integer * syntactic_primary
return And([toks[1]] * toks[0])
else:
# syntactic_primary
return [ toks[0] ]
def do_syntactic_term(str, loc, toks):
if len(toks) == 2:
# syntactic_factor - syntactic_factor
return NotAny(toks[1]) + toks[0]
else:
# syntactic_factor
return [ toks[0] ]
def do_single_definition(str, loc, toks):
toks = toks.asList()
if len(toks) > 1:
# syntactic_term , syntactic_term , ...
return And(toks)
else:
# syntactic_term
return [ toks[0] ]
def do_definitions_list(str, loc, toks):
toks = toks.asList()
if len(toks) > 1:
# single_definition | single_definition | ...
return Or(toks)
else:
# single_definition
return [ toks[0] ]
def do_syntax_rule(str, loc, toks):
# meta_identifier = definitions_list ;
assert toks[0].expr is None, "Duplicate definition"
forward_count.value -= 1
toks[0] << toks[1]
return [ toks[0] ]
def do_syntax(str, loc, toks):
# syntax_rule syntax_rule ...
return symbol_table
symbol_table = {}
class forward_count:
pass
forward_count.value = 0
for name in all_names:
expr = vars()[name]
action = vars()['do_' + name]
expr.setName(name)
expr.setParseAction(action)
#~ expr.setDebug()
def parse(ebnf, given_table={}):
symbol_table.clear()
symbol_table.update(given_table)
forward_count.value = 0
table = syntax.parseString(ebnf)[0]
assert forward_count.value == 0, "Missing definition"
for name in table:
expr = table[name]
expr.setName(name)
#~ expr.setDebug()
return table

View file

@ -1,20 +1,121 @@
import time
from builtins import builtin_map
from language_types import coerce, uncoerce
from ply import lex
class VirtualMachine(object):
def __init__(self, db):
self.db = db
self.active_task_id = None
self.sleepytime = None
self.stack = []
self.contexts = []
self.tasks = {}
self.task_sequence = []
self.ticks_used = 0
def pop(self, count=1):
return [uncoerce(self.stack.pop()) for x in range(count)]
def push(self, value):
self.stack.append(coerce(value))
def setvar(self, varname, val):
self.contexts[-1][varname] = val
def getvar(self, varname):
return self.contexts[-1][varname]
def push_context(self):
self.contexts.append({})
def pop_context(self):
self.contexts.pop()
def suspend_start_next(self, delay):
now = time.time()
newtask = heapq.heappushpop(self.task_sequence, (now+max(0.0,delay), self.stack, self.contexts, self.ticks_used))
return activate_task(newtask)
def finished_start_next(self):
now = time.time()
newtask = heapq.heappop(self.task_sequence)
return activate_task(newtask)
def activate_task(self, task):
if now < task[0]:
"task isn't ready to execute yet"
self.sleepytime = task[0] - now
heapq.heappush(self.task_sequence, task[0])
return False
self.active_task_id = task[0]
self.stack = task[1]
self.contexts = task[2]
self.ticks_used = task[3]
def get_next_task(self):
heapq.heappop(self.task_sequence)
def run_active_task
def run(self):
self.sleepytime = None
if self.active_task_id == None:
finished_start_next()
if self.active_task_id == None:
return
tokens = (
"OPERATION",
"LITERAL",
"VARIABLE"
)
class CodeOp(object):
def __init__(self):
pass
class GetProperty(CodeOp):
def execute(self, vm):
prop, obj = vm.pop(2)
vm.stack.append(vm.db.get_property(obj, prop))
t_OPERATION = (
r"CALLBUILTIN|CALLFUNC|SETVAR|RETURN"
)
class SetProperty(CodeOp):
def execute(self, vm):
val, prop, obj = vm.pop(3)
vm.db.set_property(obj, prop, val)
t_LITERAL = (
r'"i"'
)
class GetFile(CodeOp):
def execute(self, vm):
prop, obj = vm.pop(2)
vm.stack.append(vm.db.get_file(obj, prop))
lex.lex()
print lex
class SetFile(CodeOp):
def execute(self, vm):
val, prop, obj = vm.pop(3)
vm.db.set_file(obj, prop, val)
class SetVariable(CodeOp):
def execute(self, vm):
val, varname = vm.pop(2)
vm.setvar(varname, val)
class GetVariable(CodeOp):
def execute(self, vm):
varname, = vm.pop(1)
vm.push(vm.getvar(varname))
class CallBuiltin(CodeOp):
def __init__(self):
pass
def execute(self, vm):
funcname, = vm.pop(1)
builtin_map[funcname](vm)
class StartContext(CodeOp):
def execute(self, vm):
vm.push_context()
class EndContext(CodeOp):
def execute(self, vm):
vm.pop_context()

54
language_types.py Executable file
View file

@ -0,0 +1,54 @@
class VMType(object):
pass
class VMInteger(VMType):
def __init__(self, value):
self.value = int(value)
class VMFloat(VMType):
def __init__(self, value):
self.value = float(value)
class VMTable(VMType):
def __init__(self, value):
self.value = dict(value)
class VMString(VMType):
def __init__(self, value):
if isinstance(value, unicode):
self.value = value
else:
self.value = unicode(str(value), 'ascii', 'ignore')
class VMObjRef(VMType):
def __init__(self, value):
if isinstance(value, ObjRef):
self.value = value
elif isinstance(value, (float, int)):
self.value = ObjRef(int(value))
else:
raise TypeError, "Attempted to create VMObjRef with invalid object reference: %r" % (value,)
def coerce(value):
if isinstance(value, int):
return VMInteger(value)
elif isinstance(value, (tuple, list)):
return VMList(list(value))
elif isinstance(value, unicode):
return VMString(value)
elif isinstance(value, dict):
return VMTable(value)
elif isinstance(value, ObjRef):
return VMObjRef(value)
elif isinstance(value, float):
return VMFloat(value)
elif value == None:
return VMInteger(0)
else:
raise TypeError("Unknown type %s cannot be coerced to VMType" % (type(value),))
def uncoerce(value):
assert isinstance(value, VMType)
return value.value

View file

@ -2,6 +2,7 @@ import os, sys, time
import socket
import select
import random
from ringbuffer import RingBuffer
class Connection(object):
def __init__(self, id, conn, addr):
@ -11,6 +12,7 @@ class Connection(object):
self.input_encoding = 'raw'
self.output_encoding = 'ascii'
self.linebuffer = True
self.output_buffer = RingBuffer(65536, True)
def fileno(self):
return self.conn.fileno()
@ -34,6 +36,35 @@ class Connection(object):
return unicode(self.escape(data), 'ascii')
else:
return unicode(data, self.input_encoding, 'ignore')
def send(self, data):
assert isinstance(data, unicode)
try:
encoded = data.encode(self.output_encoding, 'replace')
except UnicodeEncodeError:
try:
encoded = data.encode(self.output_encoding, 'ignore')
except UnicodeEncodeError:
encoded = data.encode('ascii', 'ignore')
if len(encoded) > self.output_buffer.sizeleft():
return False
self.output_buffer.write(encoded)
return True
def output_waiting(self):
return self.output_buffer.bytes_waiting() > 0
def flush_buffer(self):
if not self.output_waiting():
return
data = self.output_buffer.read()
count = self.conn.send(data)
if count < len(data):
self.output_buffer.rewind(len(data) - count)
class Listener(object):
@ -102,7 +133,10 @@ class Listener(object):
else:
conn.close()
def send(connid, data):
conn = self.connections[connid]
conn.send(data)
def scan_for_input(self, callback):
r, w, x = select.select(self.all_connections(), [], [], 0)
@ -112,6 +146,11 @@ class Listener(object):
self.delete_connection(conn)
else:
callback(conn, input)
def flush_output(self):
for conn in self.all_connections():
if conn.output_waiting():
conn.flush_output()
def shutdown(self):
for x in self.connection_list:

103
parse.py Executable file
View file

@ -0,0 +1,103 @@
from pyparsing import *
def enum_parse():
fd = open('test.enum', 'r')
# syntax we don't want to see in the final parse tree
_lcurl = Suppress('{')
_rcurl = Suppress('}')
_equal = Suppress('=')
_comma = Suppress(',')
_semi = Suppress(';')
_enum = Suppress('enum')
identifier = Word(alphas,alphanums+'_')
integer = Word(nums)
enumValue = Group(identifier('name') + Optional(_equal + integer('value')))
enumList = Group(enumValue + ZeroOrMore(_comma + enumValue))
enum = _enum + identifier('enum') + _lcurl + enumList('list') + _rcurl + Optional(_semi)
enumlist = ZeroOrMore(enum)
#print enumlist.parseString(fd.read(), parseAll=True)
# find instances of enums ignoring other syntax
for item in enumlist.parseString(fd.read(), parseAll=True):
id = 0
for entry in item:
if entry.value != '':
id = int(entry.value)
print '%s_%s = %d' % (item.enum.upper(),entry.name.upper(),id)
id += 1
fd.close()
def parse_fourfn():
global bnf
if not bnf:
point = Literal( "." )
e = CaselessLiteral( "E" )
fnumber = Combine( Word( "+-"+nums, nums ) +
Optional( point + Optional( Word( nums ) ) ) +
Optional( e + Word( "+-"+nums, nums ) ) )
ident = Word(alphas, alphas+nums+"_$")
plus = Literal( "+" )
minus = Literal( "-" )
mult = Literal( "*" )
div = Literal( "/" )
lpar = Literal( "(" ).suppress()
rpar = Literal( ")" ).suppress()
addop = plus | minus
multop = mult | div
expop = Literal( "^" )
pi = CaselessLiteral( "PI" )
expr = Forward()
atom = (Optional("-") + ( pi | e | fnumber | ident + lpar + expr + rpar ).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )
bnf = expr
return bnf
def moo_parse():
fd = open('test.moo', 'r')
data = fd.read()
fd.close()
point = Literal( "." )
integer = Word( "+-"+nums, nums )
fnumber = Combine( integer +
Optional( point + Optional( Word( nums ) ) ) +
Optional( CaselessLiteral('e') + Word( "+-"+nums, nums ) ) )
ident = Word(alphas, alphas+nums+"_")
plus = Literal( "+" )
minus = Literal( "-" )
mult = Literal( "*" )
div = Literal( "/" )
lpar = Literal( "(" ).suppress()
rpar = Literal( ")" ).suppress()
addop = plus | minus
multop = mult | div
expop = Literal( "^" )
expr = Forward()
atom = (Optional("-") + ( fnumber | ident + lpar + expr + rpar ).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )
term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) )

203
ringbuffer.py Executable file
View file

@ -0,0 +1,203 @@
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
class FullBufferError(Exception):
class RingBuffer(object):
def __init__(self, size, overflow=True):
self.size = size
self.buffer = StringIO()
self.buffer_pos = 0
self.read_pos = 0
self.bytes_written = 0
self.first_read = True
self.allow_overflow = overflow
self.emptyclass = RingBuffer
self.fullclass = RingBufferFull
self.empty = True
def sizeleft(self):
return self.size - self.buffer_pos + self.read_pos
def bytes_waiting(self):
return self.size - self.sizeleft()
def write(self, data):
if data:
self.empty = False
ld = len(data)
if ld > self.sizeleft() and not self.allow_overflow:
raise FullBufferError("Data would overflow the buffer")
if self.buffer_pos + ld >= self.size:
self.__class__ = self.fullclass
split = self.size - self.buffer_pos
self.buffer.write(data[:split])
self.buffer.seek(0, 0)
self.buffer_pos = 0
self.bytes_written += split
self.write(data[split:])
else:
self.buffer.write(data)
self.buffer_pos += len(data)
self.bytes_written += len(data)
def read(self, bytes=0):
self.buffer.seek(self.read_pos, 0)
maxbytes = self.bytes_waiting()
if bytes > 0 and bytes < maxbytes:
maxbytes = bytes
rb = self.buffer.read(maxbytes)
self.read_pos = self.buffer.tell()
self.buffer.seek(self.buffer_pos, 0)
if self.read_pos == self.buffer_pos:
self.empty = True
return rb
def rewind(self, bytes):
if bytes > self.read_pos:
raise ValueError, "Buffer is not big enough to rewind that far"
self.read_pos -= bytes
if bytes > 0:
self.empty = False
class SplitRingBuffer(RingBuffer):
def __init__(self, size, split, overflow=True):
RingBuffer.__init__(self, size, overflow)
self.emptyclass = SplitRingBuffer
self.fullclass = SplitRingBufferFull
self.splitpos = split
self.read_pos = split
def read_head():
self.buffer.seek(0, 0)
rb = self.buffer.read()
self.buffer.seek(self.buffer_pos, 0)
return (True, rb)
class RingBufferFull(object):
def __init__(self, size):
raise NotImplementedError, "You should not create this class manually, use RingBuffer() instead"
def overflow_buffer():
self.buffer_pos = 0
self.seek_to_start()
def seek_to_start():
self.buffer.seek(0, 0)
def sizeleft(self):
if self.read_pos == None:
return self.size
elif self.read_pos == self.buffer_pos and self.empty:
return self.size
elif self.read_pos == self.buffer_pos:
return 0
elif self.read_pos < self.buffer_pos:
return self.size - self.buffer_pos + self.read_pos
else:
return self.read_pos - self.buffer_pos
def write(self, data):
if data:
self.empty = False
di = 0
ld = len(data)
# check for overflow before we start
if ld > self.sizeleft() and not self.allow_overflow:
# overflow will happen, raise an exception
raise FullBufferError("Data would overflow the buffer")
while (ld - di) + self.buffer_pos >= self.size:
# write data from the current buffer_pos all the way to the end of the ringbuffer
self.buffer.write(data[di:di + (self.size - self.buffer_pos)])
if self.read_pos != None and self.read_pos > self.buffer_pos:
# our read pos was between the buffer_pos and the end. since we just overwrote
# all that, it has been overwritten and we've lost our place, doh!
self.read_pos = None
self.overflow_buffer()
di += (self.size - self.buffer_pos)
# no more writing past the end of the buffer, now we can just do a simple write
self.buffer.write(data[di:])
if self.read_pos != None and self.buffer_pos <= self.read_pos and (self.buffer_pos + ld - di) > self.read_pos:
self.read_pos = None
self.buffer_pos += ld - di
self.bytes_written += ld
def read(self, bytes=0):
pos = self.read_pos
fullread = False
if pos == None:
pos = self.buffer_pos
maxlen = self.bytes_waiting()
self.buffer.seek(pos, 0)
if bytes > 0 and maxlen > bytes:
maxlen = bytes
if maxlen == 0:
return ''
split = self.size - pos
if split >= maxlen:
self.buffer.seek(pos, 0)
rb = self.buffer.read(maxlen)
self.read_pos = self.buffer.tell()
self.buffer.seek(self.buffer_pos, 0)
else:
self.buffer.seek(pos, 0)
rb = self.buffer.read(split)
self.seek_to_start()
rb += self.buffer.read(maxlen - split)
self.read_pos = self.buffer.tell()
self.buffer.seek(self.buffer_pos, 0)
if self.read_pos == self.buffer_pos and bytes > 0:
"we've read everything out of the buffer"
self.empty = True
return rb
def rewind(self, bytes):
if bytes > self.sizeleft():
raise ValueError, "Buffer is not big enough to rewind that far"
self.read_pos -= bytes
if self.read_pos < 0:
self.read_pos += self.size
if bytes > 0:
self.empty = False
class SplitRingBufferFull(RingBufferFull):
def read(self, bytes=0):
pass
def overflow_buffer():
self.buffer_pos = self.split_pos
def seek_to_start():
self.buffer.seek(self.split_pos, 0)
def read_head():
self.buffer.seek(0, 0)
rb = self.buffer.read(self.split_pos)
self.buffer.seek(self.buffer_pos, 0)
return (False, rb)

View file

@ -1,9 +1,18 @@
import os, sys, time
from listener import Listener
from language import VirtualMachine
from database_fake import Fake_Database as Database
class Server(object):
def __init__(self, dbname):
self.dbname = dbname
self.db = Database(dbname)
self.vm = VirtualMachine(self.db)
self.max_sleep = 0.1
self.min_sleep = 0.005
self.vm_sleep_fudge = 0.85
self.server_started = None
self.loop_started = None
self.listeners = []
def listen(self, addr, port):
@ -12,11 +21,16 @@ class Server(object):
self.listeners.append(l)
def mainloop(self):
self.server_started = time.time()
while True:
self.loop_started = time.time()
for l in self.listeners:
l.handle_incoming_connections()
l.scan_for_input(self.read_input)
time.sleep(2)
self.vm.run()
for l in self.listeners:
l.flush_output()
self.idlewait()
def read_input(self, conn, data):
@ -25,6 +39,23 @@ class Server(object):
del ds[-1]
for line in ds:
print "%s: %s" % (conn.id, line)
def idlewait(self):
if self.vm.sleepytime == None:
"virtual machine is still busy anyway, no sleeping on the job!"
return
"also check how much time we have spent already. if it's already been our polling"
"time (or longer!), there's no sense waiting even more!"
time_already_spent = time.time() - self.loop_started
sleepytime = min(self.max_sleep - time_already_spent, self.vm.sleepytime * self.vm_sleep_fudge)
if sleepytime < self.min_sleep or sleepytime < 0.0:
return
time.sleep(sleepytime)
if __name__ == "__main__":
srv = Server("Test")