├── tests ├── .keep └── test_server.py ├── .gitignore ├── marrow ├── __init__.py └── server │ ├── __init__.py │ ├── protocol.py │ ├── release.py │ ├── util.py │ ├── testing.py │ ├── pool.py │ └── base.py ├── setup.cfg ├── README.textile ├── .travis.yml ├── examples └── echo.py ├── LICENSE └── setup.py /tests/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.egg-info 4 | *.DS_Store 5 | *~ 6 | build/ 7 | dist/ 8 | tests/.coverage 9 | .coverage 10 | third-party 11 | .testing-deps -------------------------------------------------------------------------------- /marrow/__init__.py: -------------------------------------------------------------------------------- 1 | try: # pragma: no cover 2 | __import__('pkg_resources').declare_namespace(__name__) 3 | except ImportError: # pragma: no cover 4 | __import__('pkgutil').extend_path(__path__, __name__) -------------------------------------------------------------------------------- /marrow/server/__init__.py: -------------------------------------------------------------------------------- 1 | try: # pragma: no cover 2 | __import__('pkg_resources').declare_namespace(__name__) 3 | except ImportError: # pragma: no cover 4 | __import__('pkgutil').extend_path(__path__, __name__) -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | quiet = true 3 | with-coverage = true 4 | cover-package = marrow.server 5 | cover-inclusive = true 6 | where = tests 7 | detailed-errors = true 8 | 9 | [aliases] 10 | test = nosetests 11 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h2. Marrow Server API 2 | 3 | This package contains a base socket server usable to implement any number of TCP-based communication protocols. One core feature is the ability to implement both synchronous request/response protocols and asynchronous protocols. 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.2" 6 | - "3.3" 7 | install: 8 | - pip install git+https://github.com/marrow/marrow.io.git#egg=marrow.io 9 | - pip install nose coverage 10 | - pip install -e . --use-mirrors 11 | script: ./setup.py test 12 | notifications: 13 | irc: 14 | channels: "irc.freenode.org#webcore" 15 | on_success: change 16 | on_failure: change 17 | -------------------------------------------------------------------------------- /marrow/server/protocol.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | __all__ = ['Protocol'] 4 | log = __import__('logging').getLogger(__name__) 5 | 6 | 7 | 8 | class Protocol(object): 9 | def __init__(self, server, testing=False, **options): 10 | super(Protocol, self).__init__() 11 | 12 | self.server = server 13 | self.testing = testing 14 | self.options = options 15 | 16 | def start(self): 17 | pass 18 | 19 | def stop(self): 20 | pass 21 | 22 | def accept(self, stream): 23 | pass 24 | -------------------------------------------------------------------------------- /tests/test_server.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from __future__ import unicode_literals 4 | 5 | import time 6 | import socket 7 | 8 | from unittest import TestCase 9 | from functools import partial 10 | 11 | from marrow.io.iostream import IOStream 12 | from marrow.server.base import Server 13 | from marrow.server.testing import ServerTestCase 14 | from marrow.server.protocol import Protocol 15 | 16 | 17 | log = __import__('logging').getLogger(__name__) 18 | 19 | 20 | 21 | class SimpleProtocol(Protocol): 22 | def accept(self, client): 23 | log.info("Accepted connection from %r.", client.address) 24 | client.write(b"Welcome.\n", client.close) 25 | 26 | 27 | class TestProtocol(ServerTestCase): 28 | protocol = SimpleProtocol 29 | 30 | def test_serving(self): 31 | self.client.read_until(b"\n", self.stop) 32 | self.assertEquals(self.wait(), b"Welcome.\n") 33 | 34 | -------------------------------------------------------------------------------- /marrow/server/release.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """Release information about Marrow Server.""" 4 | 5 | from collections import namedtuple 6 | 7 | 8 | __all__ = ['version_info', 'version'] 9 | 10 | 11 | version_info = namedtuple('version_info', ('major', 'minor', 'micro', 'releaselevel', 'serial'))(0, 9, 0, 'final', 0) 12 | 13 | version = ".".join([str(i) for i in version_info[:3]]) + ((version_info.releaselevel[0] + str(version_info.serial)) if version_info.releaselevel != 'final' else '') 14 | 15 | 16 | name = "marrow.server" 17 | version = "0.9" 18 | release = "0.9" 19 | 20 | summary = "Abstract asynchronous, multi-process socket server API." 21 | description = """""" 22 | author = "Alice Bevan-McGregor" 23 | email = "alice+marrow@gothcandy.com" 24 | url = "http://github.com/pulp/marrow.server" 25 | download_url = "http://cheeseshop.python.org/pypi/marrow.server/" 26 | copyright = "2010, Alice Bevan-McGregor" 27 | license = "MIT" 28 | -------------------------------------------------------------------------------- /examples/echo.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | import logging 4 | 5 | from functools import partial 6 | 7 | from marrow.server.base import Server 8 | from marrow.server.protocol import Protocol 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | 14 | 15 | class EchoProtocol(Protocol): 16 | def accept(self, client): 17 | log.info("Accepted connection from %r.", client.address) 18 | 19 | client.write(b"Hello! Type something and press enter. Type /quit to quit.\n") 20 | client.read_until(b"\r\n", partial(self.on_line, client)) 21 | 22 | def on_line(self, client, data): 23 | if data[:-2] == b"/quit": 24 | client.write(b"Goodbye!\r\n", client.close) 25 | return 26 | 27 | client.write(data) 28 | client.read_until(b"\r\n", partial(self.on_line, client)) 29 | 30 | 31 | 32 | if __name__ == '__main__': 33 | import logging 34 | 35 | logging.basicConfig(level=logging.INFO) 36 | 37 | # For multi-process add "fork=" and a non-zero value. 38 | # Zero or None auto-detect the logical processors. 39 | Server(None, 8000, EchoProtocol).start() 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 Alice Bevan-McGregor and contributors. 4 | Portions copyright (c) Ian Bicking and WebOb contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /marrow/server/util.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | import select 4 | import os 5 | 6 | 7 | 8 | class WaitableEvent(object): 9 | """Provides an abstract object that can be used to resume select loops with 10 | indefinite waits from another thread or process. This mimics the standard 11 | threading.Event interface. 12 | 13 | Adapted from: 14 | http://code.activestate.com/recipes/498191-waitable-cross-process-threadingevent-class/ 15 | """ 16 | 17 | def __init__(self): 18 | self._read_fd, self._write_fd = os.pipe() 19 | 20 | def wait(self, timeout=None): 21 | rfds, wfds, efds = select.select([self._read_fd], [], [], timeout) 22 | return self._read_fd in rfds 23 | 24 | def isSet(self): 25 | return self.wait(0) 26 | 27 | def clear(self): 28 | if self.isSet(): 29 | os.read(self._read_fd, 1) 30 | 31 | def set(self): 32 | if not self.isSet(): 33 | os.write(self._write_fd, '1') 34 | 35 | def fileno(self): 36 | """Return the FD number of the read side of the pipe, allows this object to 37 | be used with select.select().""" 38 | return self._read_fd 39 | 40 | def close(self): 41 | os.close(self._read_fd) 42 | os.close(self._write_fd) 43 | -------------------------------------------------------------------------------- /marrow/server/testing.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """Unit testing helpers for asynchronous marrow.io IOLoop and IOStream. 4 | 5 | This is a barely-modified version of the unit testing rig from Tornado. 6 | """ 7 | 8 | import socket 9 | 10 | from marrow.io.testing import AsyncTestCase 11 | from marrow.io.iostream import IOStream 12 | from marrow.server.base import Server 13 | 14 | 15 | _next_port = 3000 16 | log = __import__('logging').getLogger(__name__) 17 | __all__ = ['ServerTestCase'] 18 | 19 | 20 | 21 | def get_unused_port(): 22 | global _next_port 23 | port = _next_port 24 | _next_port += 1 25 | return port 26 | 27 | 28 | class ServerTestCase(AsyncTestCase): 29 | protocol = None 30 | arguments = dict() 31 | 32 | def __init__(self, *args, **kwargs): 33 | super(ServerTestCase, self).__init__(*args, **kwargs) 34 | self.server = None 35 | self.port = None 36 | self.client = None 37 | 38 | def setUp(self): 39 | super(ServerTestCase, self).setUp() 40 | 41 | self.port = get_unused_port() 42 | self.server = Server('127.0.0.1', self.port, self.protocol, **self.arguments) 43 | self.server.start(io_loop=self.io_loop) 44 | 45 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 46 | s.connect(("127.0.0.1", self.port)) 47 | s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 48 | s.setblocking(0) 49 | 50 | self.client = IOStream(s, self.io_loop) 51 | 52 | def tearDown(self): 53 | super(ServerTestCase, self).tearDown() 54 | self.client = None 55 | self.port = None 56 | self.server = None 57 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import os 5 | import sys 6 | 7 | from setuptools import setup, find_packages 8 | 9 | 10 | if sys.version_info < (2, 6): 11 | raise SystemExit("Python 2.6 or later is required.") 12 | 13 | exec(open(os.path.join("marrow", "server", "release.py")).read()) 14 | 15 | 16 | 17 | setup( 18 | name = name, 19 | version = version, 20 | 21 | description = "A lightweight asynchronous network protocol layer.", 22 | long_description = """\ 23 | For full documentation, see the README.textile file present in the package, 24 | or view it online on the GitHub project page: 25 | 26 | https://github.com/marrow/marrow.server""", 27 | 28 | author = "Alice Bevan-McGregor", 29 | author_email = "alice+marrow@gothcandy.com", 30 | url = "https://github.com/marrow/marrow.server", 31 | license = "MIT", 32 | 33 | install_requires = [ 34 | 'marrow.io < 2.0', 35 | ], 36 | 37 | test_suite = 'nose.collector', 38 | tests_require = [ 39 | 'nose', 40 | 'coverage' 41 | ], 42 | 43 | classifiers = [ 44 | "Development Status :: 4 - Beta", 45 | "Environment :: Console", 46 | "Intended Audience :: Developers", 47 | "License :: OSI Approved :: MIT License", 48 | "Operating System :: OS Independent", 49 | "Programming Language :: Python", 50 | "Programming Language :: Python :: 2.6", 51 | "Programming Language :: Python :: 2.7", 52 | "Programming Language :: Python :: 3", 53 | "Programming Language :: Python :: 3.1", 54 | "Programming Language :: Python :: 3.2", 55 | "Topic :: Software Development :: Libraries :: Python Modules" 56 | ], 57 | 58 | packages = find_packages(exclude=['examples', 'tests']), 59 | zip_safe = True, 60 | include_package_data = True, 61 | package_data = {'': ['README.textile', 'LICENSE']}, 62 | 63 | namespace_packages = ['marrow', 'marrow.server'], 64 | ) 65 | -------------------------------------------------------------------------------- /marrow/server/pool.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """On-demand thread pool. 4 | 5 | Worker threads are spawned based on demand at the time a message is added to the queue. 6 | """ 7 | 8 | import logging 9 | import sys 10 | 11 | from math import ceil 12 | from threading import Event, Thread 13 | 14 | if sys.version_info < (3, 0): 15 | from Queue import Queue, Empty 16 | else: 17 | from queue import Queue, Empty 18 | 19 | 20 | __all__ = ['ThreadPool'] 21 | 22 | log = logging.getLogger(__name__) 23 | 24 | 25 | 26 | class ThreadPool(object): 27 | def __repr__(self): 28 | return "ThreadPool(%d jobs, %d of %d threads)" % (self.queue.qsize(), self.pool, self.maximum) 29 | 30 | def __init__(self, protocol, minimum=5, maximum=100, divisor=10, timeout=60): 31 | log.debug("Thread pool starting.") 32 | log.debug("%d threads minimum, %d maximum, %d jobs per thread, %d second timeout.", minimum, maximum, divisor, timeout) 33 | 34 | self.pool = 0 35 | self.queue = Queue() 36 | self.finished = Event() 37 | 38 | self.protocol = protocol 39 | 40 | self.minimum = minimum 41 | self.maximum = maximum 42 | self.divisor = divisor 43 | self.timeout = timeout 44 | 45 | log.debug("Spawning initial threads.") 46 | 47 | for i in range(minimum): 48 | self.spawn() 49 | 50 | log.debug("Thread pool ready.") 51 | 52 | def __call__(self, request): 53 | self.queue.put(request) 54 | optimum = self.optimum 55 | 56 | if self.pool < optimum: 57 | spawn = optimum - self.pool 58 | log.debug("Spawning %d thread%s...", spawn, '' if spawn == 1 else 's') 59 | 60 | for i in range(spawn): 61 | self.spawn() 62 | 63 | return True 64 | 65 | return False 66 | 67 | @property 68 | def optimum(self): 69 | return max(self.minimum, min(self.maximum, ceil(self.queue.qsize() / float(self.divisor)))) 70 | 71 | def stop(self): 72 | log.debug("Thread pool shutting down.") 73 | self.finished.set() 74 | 75 | log.debug("Waiting for workers to finish.") 76 | self.queue.join() 77 | 78 | log.debug("Stopping threads waiting for work.") 79 | for i in range(self.pool): 80 | self.queue.put(None) 81 | 82 | self.queue.join() 83 | 84 | def spawn(self): 85 | log.debug("Spawning thread.") 86 | thread = Thread(target=self.worker) 87 | thread.start() 88 | self.pool += 1 89 | 90 | def worker(self): 91 | log.debug("Worker thread starting up.") 92 | 93 | try: 94 | jobs = 0 95 | 96 | while True: 97 | try: 98 | request = self.queue.get(True, self.timeout) 99 | 100 | if request is None and self.finished.isSet(): 101 | log.debug("Worker death by external request.") 102 | self.queue.task_done() 103 | break 104 | 105 | self.protocol(request) 106 | jobs += 1 107 | self.queue.task_done() 108 | except Empty: 109 | if self.finished.isSet(): 110 | log.debug("Worker death by external request.") 111 | break 112 | 113 | if self.pool <= self.minimum: 114 | log.debug("Refusing to die from starvation to preserve minimum thread count.") 115 | continue 116 | 117 | log.debug("Worker death from starvation.") 118 | break 119 | 120 | if jobs == self.divisor: 121 | log.debug("Worker death form exhaustion.") 122 | 123 | if self.pool <= self.minimum: 124 | self.spawn() 125 | 126 | break 127 | except: 128 | log.exception("Internal error in worker thread.") 129 | 130 | self.pool -= 1 131 | log.debug("Worker thread finished.") 132 | 133 | 134 | 135 | if __name__ == '__main__': 136 | """This takes about 50 seconds to run on my computer.""" 137 | 138 | logging.basicConfig(level=logging.DEBUG) 139 | 140 | class Protocol(object): 141 | def request(self, request): 142 | log.info("Processing: %r", request) 143 | 144 | pool = ThreadPool(Protocol().request, minimum=1) 145 | 146 | for i in range(10000): 147 | log.warn("Adding request. Pool size: %d", pool.queue.qsize()) 148 | pool(i) 149 | 150 | pool.stop() 151 | -------------------------------------------------------------------------------- /marrow/server/base.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | """A higher-level socket server API. 4 | 5 | Uses Protocols to implement common callbacks without restricting advanced capabilities. 6 | 7 | Additionally, provides prefork and worker thread pool capabilities. 8 | """ 9 | 10 | import os 11 | import socket 12 | import time 13 | import random 14 | 15 | from inspect import isclass 16 | from binascii import hexlify 17 | 18 | try: 19 | import fcntl 20 | except ImportError: 21 | if os.name == 'nt': 22 | from marrow.io import win32_support as fcntl 23 | else: 24 | raise 25 | 26 | try: 27 | from concurrent import futures 28 | except ImportError: 29 | futures = None 30 | 31 | try: 32 | from tornado import ioloop, iostream 33 | except ImportError: 34 | from marrow.io import ioloop, iostream 35 | 36 | 37 | __all__ = ['Server'] 38 | log = __import__('logging').getLogger(__name__) 39 | 40 | 41 | 42 | class Server(object): 43 | """A basic multi-process and/or multi-threaded socket server. 44 | 45 | The protocol class attriubte should be overridden in subclasses or instances to provide actual functionality. 46 | """ 47 | 48 | protocol = None 49 | callbacks = {'start': [], 'stop': []} 50 | 51 | def __init__(self, host=None, port=None, protocol=None, pool=128, fork=1, threaded=False, **options): 52 | """Accept the minimal server configuration. 53 | 54 | If port is omitted, the host is assumed to be an on-disk UNIX domain socket file. 55 | 56 | The protocol is instantiated here, if it is a class, and passed a reference to the server and any additional arguments. 57 | 58 | If fork is None or less than 1, automatically detect the number of logical processors (i.e. cores) and fork that many copies. 59 | 60 | If threaded is False, no threading is used. If set to None, an unlimited number of threads is used (careful with that!), otherwise, if an integer, no more than that number of threads will be utilized. 61 | 62 | Do not utilze forking when you need to debug or automatically reload your code in development. 63 | """ 64 | 65 | super(Server, self).__init__() 66 | 67 | self.socket = None 68 | self.io_loop = None 69 | self.name = socket.gethostname() 70 | 71 | self.address = (host if host is not None else '', port) 72 | if protocol: self.protocol = protocol 73 | self.pool = pool 74 | self.fork = fork 75 | self.threaded = threaded 76 | self.options = options 77 | 78 | if threaded is not False and futures is None: 79 | raise NotImplementedError("You need to install the `futures` package to utilize threading.") 80 | 81 | def processors(self): 82 | try: 83 | import multiprocessing 84 | 85 | return multiprocessing.cpu_count() 86 | except ImportError: 87 | pass 88 | except NotImplementedError: 89 | pass 90 | 91 | try: 92 | return os.sysconf('SC_NPROCESSORS_CONF') 93 | except ValueError: 94 | pass 95 | 96 | log.error("Unable to automatically detect logical processor count; assuming one.") 97 | 98 | return 1 99 | 100 | def serve(self, master=True, io_loop=None): 101 | self.io_loop = io_loop or ioloop.IOLoop.instance() 102 | 103 | if isclass(self.protocol): 104 | self.protocol = self.protocol(self, io_loop, **self.options) 105 | 106 | if self.threaded is not False: 107 | log.debug("Initializing the thread pool.") 108 | self.executor = futures.ThreadPoolExecutor(max_workers=self.threaded) 109 | 110 | log.debug("Executing startup hooks.") 111 | 112 | self.protocol.start() 113 | 114 | for callback in self.callbacks['start']: 115 | callback(self) 116 | 117 | # Register for new connection notifications. 118 | self.io_loop.add_handler( 119 | self.socket.fileno(), 120 | self._accept, 121 | self.io_loop.READ 122 | ) 123 | 124 | log.info("Server running with PID %d, serving on %s.", os.getpid(), ("%s:%d" % (self.address[0] if self.address[0] else '*', self.address[1])) if isinstance(self.address, tuple) else self.address) 125 | 126 | if io_loop: return 127 | 128 | try: 129 | self.io_loop.start() 130 | except KeyboardInterrupt: 131 | log.info("Received Control+C.") 132 | except SystemExit: 133 | log.info("Received SystemExit.") 134 | raise 135 | except: 136 | log.exception("Unknown server error.") 137 | raise 138 | finally: 139 | if master: self.stop(master) 140 | else: self.io_loop.remove_handler(self.socket.fileno()) 141 | 142 | def start(self, io_loop=None): 143 | """Primary reactor loop. 144 | 145 | This handles standard signals as interpreted by Python, such as Ctrl+C. 146 | """ 147 | 148 | log.info("Starting up.") 149 | 150 | socket = self.socket = self._socket() 151 | socket.bind(self.address) 152 | socket.listen(self.pool) 153 | 154 | if self.fork is None: 155 | self.fork = self.processors() 156 | elif self.fork < 1: 157 | self.fork = min(1, self.processors() + self.fork) 158 | 159 | # Single-process operation. 160 | if self.fork == 1: 161 | self.serve(io_loop=io_loop) 162 | return 163 | 164 | # Multi-process operation. 165 | log.info("Pre-forking %d processes from PID %d.", self.fork, os.getpid()) 166 | 167 | for i in range(self.fork): 168 | if os.fork() == 0: 169 | try: 170 | random.seed(long(hexlify(os.urandom(16)), 16)) 171 | 172 | except NotImplementedError: 173 | random.seed(int(time.time() * 1000) ^ os.getpid()) 174 | 175 | self.serve(False) 176 | 177 | return 178 | 179 | try: 180 | os.waitpid(-1, 0) 181 | except OSError: 182 | pass 183 | except KeyboardInterrupt: 184 | log.info("Received Control+C.") 185 | except SystemExit: 186 | log.info("Received SystemExit.") 187 | raise 188 | except: 189 | log.exception("Unknown server error.") 190 | raise 191 | 192 | self.stop() 193 | 194 | return 195 | 196 | def stop(self, close=False, io_loop=None): 197 | log.info("Shutting down.") 198 | 199 | if self.threaded is not False: 200 | log.debug("Stopping worker thread pool; waiting for threads.") 201 | self.executor.shutdown() 202 | 203 | if self.io_loop is not None: 204 | log.debug("Executing shutdown callbacks.") 205 | 206 | # self.io_loop.remove_handler(self.socket.fileno()) 207 | self.protocol.stop() 208 | if not io_loop: self.io_loop.stop() 209 | 210 | for callback in self.callbacks['stop']: 211 | callback(self) 212 | elif close: 213 | self.socket.close() 214 | 215 | log.info("Stopped.") 216 | 217 | def _socket(self): 218 | """Create a listening socket. 219 | 220 | This handles IPv6 and allows socket re-use by spawned processes. 221 | """ 222 | 223 | host, port = self.address 224 | families = set() 225 | for family, kind, protocol, cname, sa in socket.getaddrinfo(host or None, port, flags=socket.AI_PASSIVE): 226 | families.add(family) 227 | 228 | # Default to IPv6 socket if available to enable dual stack operation 229 | family = socket.AF_INET6 if socket.AF_INET6 in families else socket.AF_INET 230 | type = socket.SOCK_STREAM | getattr(socket, 'SOCK_CLOEXEC', 0) 231 | sock = socket.socket(family, socket.SOCK_STREAM) 232 | 233 | # Prevent socket from being inherited by subprocesses (in case the SOCK_CLOEXEC flag wasn't available) 234 | flags = fcntl.fcntl(sock.fileno(), fcntl.F_GETFD) 235 | flags |= fcntl.FD_CLOEXEC 236 | fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, flags) 237 | 238 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 239 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 240 | sock.setblocking(0) 241 | 242 | # Set the IPv6-only flag if no IPv4 addresses were in the resolved address list of the given host 243 | if socket.AF_INET not in families: 244 | try: 245 | sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) 246 | except (AttributeError, socket.error): 247 | pass 248 | 249 | return sock 250 | 251 | def _accept(self, fd, events): 252 | connection, address = self.socket.accept() 253 | stream = iostream.IOStream(connection, io_loop=self.io_loop) 254 | self.protocol.accept(stream) 255 | --------------------------------------------------------------------------------