CSp/CS.py
author hh
Thu, 21 Nov 2019 14:55:10 +0100
changeset 0 5c129dd80d4f
permissions -rwxr-xr-x
--
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
0
hh
parents:
diff changeset
     1
#!/usr/bin/python3
hh
parents:
diff changeset
     2
# coding=utf8
hh
parents:
diff changeset
     3
hh
parents:
diff changeset
     4
# ring nonssl: cca 8000  rings/sec*3nodes
hh
parents:
diff changeset
     5
# ring    ssl: cca 2600  rings/sec*3nodes
hh
parents:
diff changeset
     6
# mash nonssl: cca 2000 mashes/sec*3nodes
hh
parents:
diff changeset
     7
# mash    ssl: cca  300 mashes/sec*3nodes
hh
parents:
diff changeset
     8
# u mashe je čas na přenosy úměrný počtu uzlů,
hh
parents:
diff changeset
     9
#    kdežto čas na connect/close je úměrný počtu spojů tj. kvadrátu počtu uzlů
hh
parents:
diff changeset
    10
hh
parents:
diff changeset
    11
import time
hh
parents:
diff changeset
    12
import socket
hh
parents:
diff changeset
    13
import errno
hh
parents:
diff changeset
    14
import os
hh
parents:
diff changeset
    15
import signal
hh
parents:
diff changeset
    16
import sys
hh
parents:
diff changeset
    17
import pickle
hh
parents:
diff changeset
    18
import ssl
hh
parents:
diff changeset
    19
import select
hh
parents:
diff changeset
    20
import random
hh
parents:
diff changeset
    21
import multiprocessing
hh
parents:
diff changeset
    22
import threading
hh
parents:
diff changeset
    23
hh
parents:
diff changeset
    24
hh
parents:
diff changeset
    25
class Debug():
hh
parents:
diff changeset
    26
    def __init__(self, debid):
hh
parents:
diff changeset
    27
        self.debid = debid
hh
parents:
diff changeset
    28
    def log(self, level, *msg):
hh
parents:
diff changeset
    29
        if level <= maxDebLev:
hh
parents:
diff changeset
    30
            log_lock.acquire()
hh
parents:
diff changeset
    31
            print("{:10.6f} {}:".format(time.time()-t0, self.debid), *msg, file=sys.stderr)
hh
parents:
diff changeset
    32
            sys.stderr.flush()
hh
parents:
diff changeset
    33
            log_lock.release()
hh
parents:
diff changeset
    34
    def abend(self, s):
hh
parents:
diff changeset
    35
        self.log(0, s)
hh
parents:
diff changeset
    36
        os.kill(0, signal.SIGTERM)
hh
parents:
diff changeset
    37
hh
parents:
diff changeset
    38
        
hh
parents:
diff changeset
    39
class Node(Debug):
hh
parents:
diff changeset
    40
    def __init__(self, debid, forwarding, topo, port, p0, pn, issl):
hh
parents:
diff changeset
    41
        Debug.__init__(self, "{} node {}".format(debid, port))
hh
parents:
diff changeset
    42
        self.topo = topo
hh
parents:
diff changeset
    43
        self.locPort = port
hh
parents:
diff changeset
    44
        self.p0 = p0
hh
parents:
diff changeset
    45
        self.pn = pn
hh
parents:
diff changeset
    46
        self.ssc = None
hh
parents:
diff changeset
    47
        self.cli_side = {}
hh
parents:
diff changeset
    48
        self.srv_side = {}
hh
parents:
diff changeset
    49
        self.kicker = (self.locPort == self.pn)
hh
parents:
diff changeset
    50
        self.forwarding = forwarding                # in forwarding kicker task indicates when TTL reached 0
hh
parents:
diff changeset
    51
        self.closing = False
hh
parents:
diff changeset
    52
        self.payload = None 
hh
parents:
diff changeset
    53
        self.issl = issl
hh
parents:
diff changeset
    54
        if issl:                                                                                                                                           
hh
parents:
diff changeset
    55
            self.log(4, "setting SSL context...")  
hh
parents:
diff changeset
    56
            self.sslCert = cePath + "/certs/{}.pem".format(port)
hh
parents:
diff changeset
    57
            self.sslKey = cePath + "/keys/{}.key".format(port)                                                                                
hh
parents:
diff changeset
    58
            try:                                                                                                                                                
hh
parents:
diff changeset
    59
                self.ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)                                                                                                  
hh
parents:
diff changeset
    60
                self.ctx.verify_mode = ssl.CERT_REQUIRED
hh
parents:
diff changeset
    61
                self.log(5, "cert={}, key={}".format(self.sslCert, self.sslKey))                                                                                                        
hh
parents:
diff changeset
    62
                self.ctx.load_cert_chain(self.sslCert, self.sslKey)
hh
parents:
diff changeset
    63
                self.ctx.load_verify_locations(None, caPath)                                                                              
hh
parents:
diff changeset
    64
            except ssl.SSLError as e: self.abend("SSL context: {}".format(str(e)))                                                                                                                            
hh
parents:
diff changeset
    65
    def run(self):        
hh
parents:
diff changeset
    66
        self.bind()
hh
parents:
diff changeset
    67
        self.payload = Data(self.debid, self.locPort)
hh
parents:
diff changeset
    68
        if self.kicker:
hh
parents:
diff changeset
    69
            nxt = self.next_node()
hh
parents:
diff changeset
    70
            self.log(1, "kicker ready to send '{}' to {}".format(self.payload.digest(), nxt))                                                                                                                                                                                                                                                                                       
hh
parents:
diff changeset
    71
            if not nxt in self.cli_side: self.conn(nxt)
hh
parents:
diff changeset
    72
            self.payload.put(self.cli_side[nxt])
hh
parents:
diff changeset
    73
        if self.forwarding[0].value: wait_list = (self.ssc,)
hh
parents:
diff changeset
    74
        while wait_list:
hh
parents:
diff changeset
    75
            self.log(4, "select...")
hh
parents:
diff changeset
    76
            self.log(5, "waitlist:", *(sc.fileno() for sc in wait_list))
hh
parents:
diff changeset
    77
            ready_list = select.select(wait_list, (), (), sel_TO)
hh
parents:
diff changeset
    78
            self.log(5, "readylist:", *(sc.fileno() for sc in ready_list[0]))
hh
parents:
diff changeset
    79
            for sc in ready_list[0]:
hh
parents:
diff changeset
    80
                self.log(5, "sc {} ready...".format(sc.fileno()))
hh
parents:
diff changeset
    81
                if sc == self.ssc: sc = self.acc()                    
hh
parents:
diff changeset
    82
                self.forward(sc)
hh
parents:
diff changeset
    83
            wait_list = ()
hh
parents:
diff changeset
    84
            self.log(4, "forwarding={}".format(self.forwarding[0].value)) 
hh
parents:
diff changeset
    85
            if self.forwarding[0].value: wait_list += (self.ssc,)            # when off, no new connection will come on ssc 
hh
parents:
diff changeset
    86
            else: self.close_cli()
hh
parents:
diff changeset
    87
            for sc in self.srv_side.values(): wait_list += (sc,)
hh
parents:
diff changeset
    88
        self.close_srv()
hh
parents:
diff changeset
    89
        signal.signal(signal.SIGUSR2, sighand)
hh
parents:
diff changeset
    90
        last = 0
hh
parents:
diff changeset
    91
        ctr_lock.acquire()
hh
parents:
diff changeset
    92
        active.value -= 1
hh
parents:
diff changeset
    93
        if active.value == 0: last = 1        
hh
parents:
diff changeset
    94
        ctr_lock.release()
hh
parents:
diff changeset
    95
        if last: os.kill(0, signal.SIGUSR2)
hh
parents:
diff changeset
    96
        else: signal.pause()
hh
parents:
diff changeset
    97
        self.log(2, "ended")
hh
parents:
diff changeset
    98
        os._exit(0)
hh
parents:
diff changeset
    99
    def forward(self, sc):
hh
parents:
diff changeset
   100
        if not self.payload.get(sc):
hh
parents:
diff changeset
   101
            self.log(5, "delete srv_side[{}]".format(sc.fileno()))
hh
parents:
diff changeset
   102
            del self.srv_side[sc]
hh
parents:
diff changeset
   103
            self.log(4, "closing {}...".format(sc.fileno()))
hh
parents:
diff changeset
   104
            sc.close()
hh
parents:
diff changeset
   105
        else:
hh
parents:
diff changeset
   106
            self.log(5, "received data from {}".format(self.payload.rport))
hh
parents:
diff changeset
   107
            ctr_lock.acquire(); 
hh
parents:
diff changeset
   108
            forwards.value += 1; 
hh
parents:
diff changeset
   109
            ctr_lock.release()
hh
parents:
diff changeset
   110
            if self.kicker:
hh
parents:
diff changeset
   111
                self.payload.dttl()
hh
parents:
diff changeset
   112
                self.log(3, "received from {}: {}, ttl={}".format(self.topo, self.payload.digest(), self.payload.ttl))                                       
hh
parents:
diff changeset
   113
                if self.payload.ttl <= 0:                                                                                                                   
hh
parents:
diff changeset
   114
                    self.log(1, "received after passing all {}: {}".\
hh
parents:
diff changeset
   115
                             format("mashes" if self.topo == "mash" else "rings", self.payload.digest()))
hh
parents:
diff changeset
   116
                    self.forwarding[0].value = 0                                                                                                                    
hh
parents:
diff changeset
   117
                    return                                                                                                                     
hh
parents:
diff changeset
   118
            nxt = self.next_node()
hh
parents:
diff changeset
   119
            self.log(5, "forwarding to {}...".format(nxt))
hh
parents:
diff changeset
   120
            if pacing: time.sleep(pace)                                                                                                                                                                                                                                                                                       
hh
parents:
diff changeset
   121
            if not nxt in self.cli_side: self.conn(nxt)
hh
parents:
diff changeset
   122
            self.payload.put(self.cli_side[nxt])
hh
parents:
diff changeset
   123
            self.log(5, "forwarded to {}".format(nxt))
hh
parents:
diff changeset
   124
    def next_node(self):
hh
parents:
diff changeset
   125
        if self.topo == "ring":                                                                                                                                 
hh
parents:
diff changeset
   126
            if self.kicker: nxt = self.p0                                                                                                                      
hh
parents:
diff changeset
   127
            else: nxt = self.locPort + 1                                                                                                                       
hh
parents:
diff changeset
   128
        else:                                                                                                                                                   
hh
parents:
diff changeset
   129
            nxt = self.locPort                                                                                                                                 
hh
parents:
diff changeset
   130
            while nxt == self.locPort:                                                                                                                        
hh
parents:
diff changeset
   131
                nxt = random.randint(self.p0, self.pn)                                                                                                         
hh
parents:
diff changeset
   132
        return nxt        
hh
parents:
diff changeset
   133
    def bind(self):
hh
parents:
diff changeset
   134
            self.log(4, "binding...")
hh
parents:
diff changeset
   135
            try:
hh
parents:
diff changeset
   136
                self.ssc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
hh
parents:
diff changeset
   137
                self.ssc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
hh
parents:
diff changeset
   138
            except Exception as e: 
hh
parents:
diff changeset
   139
                self.abend("ssc alloc: {}".format(e.strerror))
hh
parents:
diff changeset
   140
            if self.issl:
hh
parents:
diff changeset
   141
                self.log(4, "ssc SSL wrap")
hh
parents:
diff changeset
   142
                try: self.ssc = self.ctx.wrap_socket(self.ssc)
hh
parents:
diff changeset
   143
                except ssl.SSLError as e: self.abend("ssc SSL wrap: {}".format(str(e)))
hh
parents:
diff changeset
   144
            try:
hh
parents:
diff changeset
   145
                self.ssc.bind(("127.0.0.1", self.locPort))
hh
parents:
diff changeset
   146
                self.ssc.listen(1)
hh
parents:
diff changeset
   147
            except Exception as e: self.abend("bind: {}".format(e.strerror))
hh
parents:
diff changeset
   148
            self.log(2, "bound")
hh
parents:
diff changeset
   149
    def conn(self, remPort):
hh
parents:
diff changeset
   150
        self.log(4, "connecting to {}...".format(remPort))
hh
parents:
diff changeset
   151
        try: sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
hh
parents:
diff changeset
   152
        except Exception as e: self.abend("socket alloc: {}".format(e.strerror))
hh
parents:
diff changeset
   153
        if self.issl:
hh
parents:
diff changeset
   154
            self.log(4, "sc SSL wrap")
hh
parents:
diff changeset
   155
            try: sc = self.ctx.wrap_socket(sc)
hh
parents:
diff changeset
   156
            except ssl.SSLError as e: self.abend("sc SSL wrap: {}".format(str(e)))
hh
parents:
diff changeset
   157
        retry = connThreshold
hh
parents:
diff changeset
   158
        connected = False
hh
parents:
diff changeset
   159
        while not connected and retry > 0:
hh
parents:
diff changeset
   160
            try:
hh
parents:
diff changeset
   161
                sc.connect(("127.0.0.1", remPort))
hh
parents:
diff changeset
   162
                connected = True
hh
parents:
diff changeset
   163
            except Exception as e:
hh
parents:
diff changeset
   164
                if e.errno == errno.ECONNREFUSED:
hh
parents:
diff changeset
   165
                    retry = retry - 1
hh
parents:
diff changeset
   166
                    time.sleep(conn_TO)
hh
parents:
diff changeset
   167
                else: self.abend("connect: {}".format(str(e)))
hh
parents:
diff changeset
   168
        if retry == 0: self.abend("connection refused, threshold {} reached".format(connThreshold))
hh
parents:
diff changeset
   169
        ctr_lock.acquire();
hh
parents:
diff changeset
   170
        connects.value += 1;
hh
parents:
diff changeset
   171
        ctr_lock.release()
hh
parents:
diff changeset
   172
        try: self.cli_side[remPort] = sc.makefile("wb")
hh
parents:
diff changeset
   173
        except Exception as e: self.abend("client side makefile: {}".format(str(e)))
hh
parents:
diff changeset
   174
        self.log(2, "connected to {} after {} retries".format(remPort, connThreshold - retry))
hh
parents:
diff changeset
   175
    def acc(self):
hh
parents:
diff changeset
   176
        self.log(4, "accepting...")
hh
parents:
diff changeset
   177
        try:
hh
parents:
diff changeset
   178
            ac = self.ssc.accept()
hh
parents:
diff changeset
   179
            sc = ac[0]
hh
parents:
diff changeset
   180
        except Exception as e: self.abend("accept: {}".format(str(e)))
hh
parents:
diff changeset
   181
        try: sc = sc.makefile("rb")
hh
parents:
diff changeset
   182
        except Exception as e: self.abend("srv side makefile: {}".format(str(e)))
hh
parents:
diff changeset
   183
        self.srv_side[sc] = sc
hh
parents:
diff changeset
   184
        self.log(2, "accepted on sc={}, addr={}".format(sc.fileno(), ac[1]))
hh
parents:
diff changeset
   185
        return sc
hh
parents:
diff changeset
   186
    def close_srv(self):
hh
parents:
diff changeset
   187
        self.log(5, "closing ssc...")
hh
parents:
diff changeset
   188
        if self.ssc: self.ssc.close()
hh
parents:
diff changeset
   189
    def close_cli(self):
hh
parents:
diff changeset
   190
        def do_close():
hh
parents:
diff changeset
   191
            self.log(5, "closing clients...")
hh
parents:
diff changeset
   192
            scs = self.cli_side.values()
hh
parents:
diff changeset
   193
            self.cli_side.clear()
hh
parents:
diff changeset
   194
            for sc in scs: sc.close()
hh
parents:
diff changeset
   195
            self.log(4, "all clients closed")
hh
parents:
diff changeset
   196
        if not self.closing:
hh
parents:
diff changeset
   197
            threading.Thread(target = do_close, name = "client {} close".format(self.locPort)).start()
hh
parents:
diff changeset
   198
            self.closing = True
hh
parents:
diff changeset
   199
hh
parents:
diff changeset
   200
hh
parents:
diff changeset
   201
class Data(Debug):
hh
parents:
diff changeset
   202
    def __init__(self, debid, port):
hh
parents:
diff changeset
   203
        self.debid = debid + " payload"
hh
parents:
diff changeset
   204
        self.clear()
hh
parents:
diff changeset
   205
        self.ttl = ittl
hh
parents:
diff changeset
   206
        self.lport = port
hh
parents:
diff changeset
   207
        self.rport = 0
hh
parents:
diff changeset
   208
        self.text = ""
hh
parents:
diff changeset
   209
    def clear(self):
hh
parents:
diff changeset
   210
        (self.ttl, self.rport, self.text) = 3 * (None,)
hh
parents:
diff changeset
   211
    def put(self, sc):
hh
parents:
diff changeset
   212
        self.log(5, "sending via {}...".format(sc.fileno()))
hh
parents:
diff changeset
   213
        if self.ttl == None: self.ttl = ittl
hh
parents:
diff changeset
   214
        if self.text == None: self.text = itext
hh
parents:
diff changeset
   215
        try:
hh
parents:
diff changeset
   216
            pickle.dump((self.ttl, self.lport, self.text), sc)
hh
parents:
diff changeset
   217
            sc.flush()
hh
parents:
diff changeset
   218
        except Exception as e: self.abend("send: {}".format(str(e)))
hh
parents:
diff changeset
   219
    def get(self, sc):
hh
parents:
diff changeset
   220
        self.log(5, "reading from {}...".format(sc.fileno()))
hh
parents:
diff changeset
   221
        self.clear()
hh
parents:
diff changeset
   222
        try:
hh
parents:
diff changeset
   223
            (self.ttl, self.rport, self.text) = pickle.load(sc)
hh
parents:
diff changeset
   224
            return True
hh
parents:
diff changeset
   225
        except Exception as e:
hh
parents:
diff changeset
   226
            if isinstance(e, EOFError): return False
hh
parents:
diff changeset
   227
            else: self.abend("receive: {}".format(str(e)))
hh
parents:
diff changeset
   228
    def dttl(self):
hh
parents:
diff changeset
   229
        self.ttl -= 1
hh
parents:
diff changeset
   230
        return self.ttl
hh
parents:
diff changeset
   231
    def digest(self):
hh
parents:
diff changeset
   232
        return self.text if len(self.text) < 24 else self.text[0:8]+"--------"+self.text[-8:]
hh
parents:
diff changeset
   233
    def toString(self):
hh
parents:
diff changeset
   234
        return "ttl={}, from port={}, text={}".\
hh
parents:
diff changeset
   235
            format(str(self.ttl), str(self.rport), self.digest())
hh
parents:
diff changeset
   236
hh
parents:
diff changeset
   237
class Constellation(Debug):
hh
parents:
diff changeset
   238
    def __init__(self, issl, topo, p0, n):
hh
parents:
diff changeset
   239
        Debug.__init__(self, "{}SSL {}".format("" if issl else "non", topo.upper()))
hh
parents:
diff changeset
   240
    def run(self, issl, topo, p0, n):
hh
parents:
diff changeset
   241
        signal.signal(signal.SIGUSR2, signal.SIG_IGN)
hh
parents:
diff changeset
   242
        forwarding = [multiprocessing.Value('i', 1, lock=False)]                    # list is passed by reference
hh
parents:
diff changeset
   243
        if n == 1:
hh
parents:
diff changeset
   244
            self.log(0, "one-node configuration is not implemented")
hh
parents:
diff changeset
   245
        else:
hh
parents:
diff changeset
   246
            self.log(1, "{} nodes starting...".format(n))
hh
parents:
diff changeset
   247
            p0 += 500 if issl else 0
hh
parents:
diff changeset
   248
            pn = p0 + n - 1
hh
parents:
diff changeset
   249
            for port in range(p0, p0 + n):
hh
parents:
diff changeset
   250
                pid = os.fork()
hh
parents:
diff changeset
   251
                if not pid: Node(self.debid, forwarding, topo, port, p0, pn, issl).run()
hh
parents:
diff changeset
   252
                else: self.log(4, "node {} started in process {}".format(port, pid))
hh
parents:
diff changeset
   253
        self.log(2, "all nodes established")
hh
parents:
diff changeset
   254
        while 1:
hh
parents:
diff changeset
   255
            try:
hh
parents:
diff changeset
   256
                p = os.wait()
hh
parents:
diff changeset
   257
                if p[1] & 255:
hh
parents:
diff changeset
   258
                    self.log(4, "pid {} killed by {}".format(p[0], p[1] & 255))
hh
parents:
diff changeset
   259
                else:
hh
parents:
diff changeset
   260
                    self.log(4, "pid {} returned  {}".format(p[0], p[1] >> 8))
hh
parents:
diff changeset
   261
            except: break
hh
parents:
diff changeset
   262
        os._exit(0)
hh
parents:
diff changeset
   263
hh
parents:
diff changeset
   264
def ga(key, default):
hh
parents:
diff changeset
   265
    return os.environ[key] if key in os.environ else default
hh
parents:
diff changeset
   266
def gi(key, default):
hh
parents:
diff changeset
   267
    return int(ga(key, default))
hh
parents:
diff changeset
   268
def sighand(signal, frame):
hh
parents:
diff changeset
   269
    pass
hh
parents:
diff changeset
   270
hh
parents:
diff changeset
   271
debug = Debug("client/server demo")
hh
parents:
diff changeset
   272
log_lock = multiprocessing.Lock()
hh
parents:
diff changeset
   273
ctr_lock = multiprocessing.Lock()
hh
parents:
diff changeset
   274
forwards = multiprocessing.Value('i', 0)
hh
parents:
diff changeset
   275
connects = multiprocessing.Value('i', 0)
hh
parents:
diff changeset
   276
active = multiprocessing.Value('i', 0)
hh
parents:
diff changeset
   277
t0 = time.time()
hh
parents:
diff changeset
   278
maxDebLev = gi('DEB', 0)
hh
parents:
diff changeset
   279
mn = rn = gi('N', 0)
hh
parents:
diff changeset
   280
rp0 = gi('RP0', 11000)
hh
parents:
diff changeset
   281
rn = gi('RN', 3)
hh
parents:
diff changeset
   282
mp0 = gi('MP0', 12000)
hh
parents:
diff changeset
   283
mn = gi('MN', 3)
hh
parents:
diff changeset
   284
itext = ga('T', "bla bla")
hh
parents:
diff changeset
   285
ittl = gi('TTL', 3)
hh
parents:
diff changeset
   286
pace = float(os.environ["P"]) if "P" in os.environ else 0
hh
parents:
diff changeset
   287
pacing = 1 if pace > 0 else 0
hh
parents:
diff changeset
   288
random.seed(gi('RS', 0))
hh
parents:
diff changeset
   289
connThreshold = 77
hh
parents:
diff changeset
   290
conn_TO = 0.01
hh
parents:
diff changeset
   291
sel_TO = 1
hh
parents:
diff changeset
   292
issl = gi('SSL', 0)
hh
parents:
diff changeset
   293
caPath = "/home/local/etc/ssl/certs/"
hh
parents:
diff changeset
   294
sslPathSuff = "/../CS/"
hh
parents:
diff changeset
   295
cePath = os.environ["CEP"] if "CEP" in os.environ else os.path.dirname(sys.argv[0]) + sslPathSuff
hh
parents:
diff changeset
   296
active.value = mn + rn
hh
parents:
diff changeset
   297
if issl > 1: active.value *= 2
hh
parents:
diff changeset
   298
signal.signal(signal.SIGUSR2, signal.SIG_IGN)
hh
parents:
diff changeset
   299
debug.log(1, "pgm={}, ttl={}, pace={}, seed={}, SSL mask={}, debug={}".format(sys.argv[0], ittl, pace, gi('RS', 0), issl, maxDebLev))
hh
parents:
diff changeset
   300
if issl > 0: debug.log(3, "ssl path: {}, CA path: {}".format(cePath, caPath))
hh
parents:
diff changeset
   301
          
hh
parents:
diff changeset
   302
if issl < 2: issl = (issl,)
hh
parents:
diff changeset
   303
else: issl = (0, 1)
hh
parents:
diff changeset
   304
if ittl > 0:
hh
parents:
diff changeset
   305
    for ss in issl:
hh
parents:
diff changeset
   306
        topo = ("mash", "ring")
hh
parents:
diff changeset
   307
        p0 = (mp0, rp0)
hh
parents:
diff changeset
   308
        n = (mn, rn)
hh
parents:
diff changeset
   309
        for p in zip(topo, p0, n): 
hh
parents:
diff changeset
   310
            if not os.fork(): 
hh
parents:
diff changeset
   311
                Constellation(ss, *p).run(ss, *p)
hh
parents:
diff changeset
   312
    while 1:
hh
parents:
diff changeset
   313
        try: os.wait()
hh
parents:
diff changeset
   314
        except: break
hh
parents:
diff changeset
   315
debug.log(1, "final balance: forwards={}, connections={}".format(forwards.value, connects.value))