kivy / oscpy

An efficient OSC implementation compatible with python2.7 and 3.5+
MIT License
111 stars 25 forks source link

Bad File Description on close #20

Closed melMass closed 5 years ago

melMass commented 5 years ago
Exception in thread Thread-42:
Traceback (most recent call last):
  File threading.py, line 801, in __bootstrap_inner
    self.run()
  File threading.py, line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "oscpy/server.py", line 307, in _listen
    read, write, error = select(self.sockets, [], [], self.timeout)
error: (9, 'Bad file descriptor')

When I try to close using osc.stop_all() the above error happens every time.

Tested on Python 2.7 and 3.6 resulting in the same error

tshirtman commented 5 years ago

@melMass i can't reproduce it on my side, can you give more context about how you trigger it (OS, example program triggering it, and whatever you think might be interresting)?

from oscpy.server import OSCThreadServer
from oscpy.client import OSCClient
from time import sleep

def callback(*values):
    print("got values: {}".format(values))

osc = OSCThreadServer(encoding='utf8')
sock = osc.listen(address='0.0.0.0', port=8000, default=True)
osc.bind(b'/address', callback)
client = OSCClient('localhost', 8000, encoding='utf8')
for i in range(10000):
    client.send_message('/address', range(10))

osc.stop_all()
melMass commented 5 years ago

Thanks @tshirtman.

I'll try come back to you tomorrow as I don't have the project with me. But if I remember right here are some more info:

Hope it helps, coming back with code bits and more precisions tomorrow

tshirtman commented 5 years ago

Ok, i though this would help, but no dice yet

from oscpy.server import OSCThreadServer
from oscpy.client import send_message
from time import sleep
import gc

class OSCMachine(object):
    def __init__(self, port):
        self.port = port
        self.osc = osc = OSCThreadServer(encoding='utf8')
        sock = osc.listen(address='0.0.0.0', port=port, default=True)
        osc.bind(b'/address', self.callback)

    def callback(self, *values):
        print("{}: got values: {}".format(self.port, values))
        pass

    def delete(self):
        self.osc.stop_all()
        print("stopped: {}".format(self.port))

def run():
    servers = {}
    for i in range(5):
        servers[i] = OSCMachine(port=8000 + i)

    for i in range(40):
        if not i % 10:
            (servers[i // 10]).delete()

        if not (i - 5) % 10:
            servers[i] = OSCMachine(port=8000 + i)

        port = 8000 + list(servers.keys())[i % len(servers)]
        send_message('/address', range(10), 'localhost', port, encoding='utf8')
        sleep(.1)

    while servers:
        servers.pop(list(servers.keys())[0]).delete()
        sleep(.1)

if __name__ == '__main__':
    run()

this works fine for me, (tried using __del__ but somehow it's never called), i don't get any traceback in the log

stopped: 8000
8001: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8002: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8003: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8004: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8005: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8001: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8002: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8003: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
stopped: 8001
8004: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8005: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8002: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8002: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8003: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8004: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8005: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
stopped: 8002
8015: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8003: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8003: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8004: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8005: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
stopped: 8003
8015: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8025: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
8035: got values: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
stopped: 8000
stopped: 8001
stopped: 8002
stopped: 8003
stopped: 8004
stopped: 8005
stopped: 8015
stopped: 8025
stopped: 8035
melMass commented 5 years ago

@tshirtman Thanks for the test and the reminder.

So basicly I'm using a Client UI & Server UI (One to send and one to receive)

The client using OSCClient as follow:

from oscpy.client import OSCClient

class Client(QWidget):
  def __init__(self):
    super().__init__()
    layout = QVBoxLayout(self)
    self.osc = OSCClient('0.0.0.0',5005)

    self.color = Dial("Color")
    self.contrast = Dial("Contrast")
    self.offset = Dial("Offset")

    self.color.valueChanged.connect(self.sendValue)
    self.contrast.valueChanged.connect(self.sendValue)
    self.offset.valueChanged.connect(self.sendValue)

  def sendValue (self, path, value):
    self.osc.send_message("/{}".format(path).encode(),[value])

And the server:

class Server(QWidget):
  def __init__(self,ip="0.0.0.0",port=5005):
    super().__init__()
    layout = QVBoxLayout(self)
    self.ip = ip
    self.port = port
    dispatch = ["color","contrast", "offset"]

    self.osc = OSCThreadServer()
    self.sock = self.osc.listen(address=ip, port=port, default=True)
    for d in dispatch:
      self.osc.bind("/{}".format(d).encode(), partial(self.receive,d))

  def receive(self,path,value):
    # updates the UI infos
    self.serverInfo.set(path,value)

    if path == 'contrast':
      self.img.fill(QColor(255,value/127*255,0))
      self.colorPreview.setPixmap(QPixmap.fromImage(self.img))

    ...

   def closeEvent(self,evt):
    print("BYE")
    self.osc.close()
    super().closeEvent(evt) 

All works as expected but whenever I'm either destroying or closing the server it flushes:

BYE
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/Users/imac/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/Users/imac/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "oscpy/server.py", line 307, in _listen
    read, write, error = select(self.sockets, [], [], self.timeout)
OSError: [Errno 9] Bad file descriptor

this works fine for me, (tried using __del__ but somehow it's never called), i don't get any traceback in the log

I suspect this to be related to my issue.

tshirtman commented 5 years ago

decided to catch the errors and go on, as it seems to be an expected behavior when closing a socket while doing select or recv, please reopen if it causes other issues.