diff --git a/lib/assets/Lib/select.py b/lib/assets/Lib/select.py new file mode 100644 --- /dev/null +++ b/lib/assets/Lib/select.py @@ -0,0 +1,268 @@ +""" +borrowed from jython +https://bitbucket.org/jython/jython/raw/28a66ba038620292520470a0bb4dc9bb8ac2e403/Lib/select.py +""" + +#import java.nio.channels.SelectableChannel +#import java.nio.channels.SelectionKey +#import java.nio.channels.Selector +#from java.nio.channels.SelectionKey import OP_ACCEPT, OP_CONNECT, OP_WRITE, OP_READ + +import errno +import os +import queue +import socket + +class error(Exception): pass + +ALL = None + +_exception_map = { + +# (, ) : lambda: + +#(java.nio.channels.ClosedChannelException, ALL) : error(errno.ENOTCONN, 'Socket is not connected'), +#(java.nio.channels.CancelledKeyException, ALL) : error(errno.ENOTCONN, 'Socket is not connected'), +#(java.nio.channels.IllegalBlockingModeException, ALL) : error(errno.ESOCKISBLOCKING, 'socket must be in non-blocking mode'), +} + +def _map_exception(exc, circumstance=ALL): + try: + mapped_exception = _exception_map[(exc.__class__, circumstance)] + mapped_exception.java_exception = exc + return mapped_exception + except KeyError: + return error(-1, 'Unmapped java exception: <%s:%s>' % (exc.toString(), circumstance)) + +POLLIN = 1 +POLLOUT = 2 + +# The following event types are completely ignored on jython +# Java does not support them, AFAICT +# They are declared only to support code compatibility with cpython + +POLLPRI = 4 +POLLERR = 8 +POLLHUP = 16 +POLLNVAL = 32 + +def _getselectable(selectable_object): + try: + channel = selectable_object.getchannel() + except: + try: + channel = selectable_object.fileno().getChannel() + except: + raise TypeError("Object '%s' is not watchable" % selectable_object, + errno.ENOTSOCK) + + if channel and not isinstance(channel, java.nio.channels.SelectableChannel): + raise TypeError("Object '%s' is not watchable" % selectable_object, + errno.ENOTSOCK) + return channel + +class poll: + + def __init__(self): + self.selector = java.nio.channels.Selector.open() + self.chanmap = {} + self.unconnected_sockets = [] + + def _register_channel(self, socket_object, channel, mask): + jmask = 0 + if mask & POLLIN: + # Note that OP_READ is NOT a valid event on server socket channels. + if channel.validOps() & OP_ACCEPT: + jmask = OP_ACCEPT + else: + jmask = OP_READ + if mask & POLLOUT: + if channel.validOps() & OP_WRITE: + jmask |= OP_WRITE + if channel.validOps() & OP_CONNECT: + jmask |= OP_CONNECT + selectionkey = channel.register(self.selector, jmask) + self.chanmap[channel] = (socket_object, selectionkey) + + def _check_unconnected_sockets(self): + temp_list = [] + for socket_object, mask in self.unconnected_sockets: + channel = _getselectable(socket_object) + if channel is not None: + self._register_channel(socket_object, channel, mask) + else: + temp_list.append( (socket_object, mask) ) + self.unconnected_sockets = temp_list + + def register(self, socket_object, mask = POLLIN|POLLOUT|POLLPRI): + try: + channel = _getselectable(socket_object) + if channel is None: + # The socket is not yet connected, and thus has no channel + # Add it to a pending list, and return + self.unconnected_sockets.append( (socket_object, mask) ) + return + self._register_channel(socket_object, channel, mask) + except BaseException: + #except java.lang.Exception, jlx: + raise _map_exception(jlx) + + def unregister(self, socket_object): + try: + channel = _getselectable(socket_object) + self.chanmap[channel][1].cancel() + del self.chanmap[channel] + except BaseException: + #except java.lang.Exception, jlx: + raise _map_exception(jlx) + + def _dopoll(self, timeout): + if timeout is None or timeout < 0: + self.selector.select() + else: + try: + timeout = int(timeout) + if not timeout: + self.selector.selectNow() + else: + # No multiplication required: both cpython and java use millisecond timeouts + self.selector.select(timeout) + except ValueError as vx: + raise error("poll timeout must be a number of milliseconds or None", errno.EINVAL) + # The returned selectedKeys cannot be used from multiple threads! + return self.selector.selectedKeys() + + def poll(self, timeout=None): + try: + self._check_unconnected_sockets() + selectedkeys = self._dopoll(timeout) + results = [] + for k in selectedkeys.iterator(): + jmask = k.readyOps() + pymask = 0 + if jmask & OP_READ: pymask |= POLLIN + if jmask & OP_WRITE: pymask |= POLLOUT + if jmask & OP_ACCEPT: pymask |= POLLIN + if jmask & OP_CONNECT: pymask |= POLLOUT + # Now return the original userobject, and the return event mask + results.append( (self.chanmap[k.channel()][0], pymask) ) + return results + except BaseException: + #except java.lang.Exception, jlx: + raise _map_exception(jlx) + + def _deregister_all(self): + try: + for k in self.selector.keys(): + k.cancel() + # Keys are not actually removed from the selector until the next select operation. + self.selector.selectNow() + except BaseException: + #except java.lang.Exception, jlx: + raise _map_exception(jlx) + + def close(self): + try: + self._deregister_all() + self.selector.close() + except BaseException: + #except java.lang.Exception, jlx: + raise _map_exception(jlx) + +def _calcselecttimeoutvalue(value): + if value is None: + return None + try: + floatvalue = float(value) + except Exception as x: + raise TypeError("Select timeout value must be a number or None") + if value < 0: + raise error("Select timeout value cannot be negative", errno.EINVAL) + if floatvalue < 0.000001: + return 0 + return int(floatvalue * 1000) # Convert to milliseconds + +# This cache for poll objects is required because of a bug in java on MS Windows +# http://bugs.jython.org/issue1291 + +class poll_object_cache: + + def __init__(self): + self.is_windows = os.name == 'nt' + if self.is_windows: + self.poll_object_queue = Queue.Queue() + import atexit + atexit.register(self.finalize) + + def get_poll_object(self): + if not self.is_windows: + return poll() + try: + return self.poll_object_queue.get(False) + except Queue.Empty: + return poll() + + def release_poll_object(self, pobj): + if self.is_windows: + pobj._deregister_all() + self.poll_object_queue.put(pobj) + else: + pobj.close() + + def finalize(self): + if self.is_windows: + while True: + try: + p = self.poll_object_queue.get(False) + p.close() + except Queue.Empty: + return + +_poll_object_cache = poll_object_cache() + +def native_select(read_fd_list, write_fd_list, outofband_fd_list, timeout=None): + timeout = _calcselecttimeoutvalue(timeout) + # First create a poll object to do the actual watching. + pobj = _poll_object_cache.get_poll_object() + try: + registered_for_read = {} + # Check the read list + for fd in read_fd_list: + pobj.register(fd, POLLIN) + registered_for_read[fd] = 1 + # And now the write list + for fd in write_fd_list: + if fd in registered_for_read: + # registering a second time overwrites the first + pobj.register(fd, POLLIN|POLLOUT) + else: + pobj.register(fd, POLLOUT) + results = pobj.poll(timeout) + # Now start preparing the results + read_ready_list, write_ready_list, oob_ready_list = [], [], [] + for fd, mask in results: + if mask & POLLIN: + read_ready_list.append(fd) + if mask & POLLOUT: + write_ready_list.append(fd) + return read_ready_list, write_ready_list, oob_ready_list + finally: + _poll_object_cache.release_poll_object(pobj) + +select = native_select + +def cpython_compatible_select(read_fd_list, write_fd_list, outofband_fd_list, timeout=None): + # First turn all sockets to non-blocking + # keeping track of which ones have changed + modified_channels = [] + try: + for socket_list in [read_fd_list, write_fd_list, outofband_fd_list]: + for s in socket_list: + channel = _getselectable(s) + if channel.isBlocking(): + modified_channels.append(channel) + channel.configureBlocking(0) + return native_select(read_fd_list, write_fd_list, outofband_fd_list, timeout) + finally: + for channel in modified_channels: + channel.configureBlocking(1)