import socket import fcntl import os, os.path import time import warnings from datetime import datetime, timedelta from tornado.escape import json_encode from config import server_socket_path class WebtilesSocketConnection(object): def __init__(self, io_loop, socketpath, logger): self.io_loop = io_loop self.crawl_socketpath = socketpath self.logger = logger self.message_callback = None self.socket = None self.socketpath = None self.open = False self.close_callback = None self.msg_buffer = None def connect(self, primary = True): if not os.path.exists(self.crawl_socketpath): # Wait until the socket exists self.io_loop.add_timeout(time.time() + 1, self.connect) return self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) self.socket.settimeout(10) # Set close-on-exec flags = fcntl.fcntl(self.socket.fileno(), fcntl.F_GETFD) fcntl.fcntl(self.socket.fileno(), flags | fcntl.FD_CLOEXEC) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Bind to a temp path # Ignore the security warning about tempnam; in this case, # there is no security risk (the most that can happen is that # the bind call fails) with warnings.catch_warnings(): warnings.simplefilter("ignore") self.socketpath = os.tempnam(server_socket_path, "crawl") self.socket.bind(self.socketpath) # Install handler self.io_loop.add_handler(self.socket.fileno(), self._handle_read, self.io_loop.ERROR | self.io_loop.READ) msg = json_encode({ "msg": "attach", "primary": primary }) self.open = True self.send_message(msg) def _handle_read(self, fd, events): if events & self.io_loop.READ: data = self.socket.recv(128 * 1024, socket.MSG_DONTWAIT) self._handle_data(data) if events & self.io_loop.ERROR: pass def _handle_data(self, data): if self.msg_buffer is not None: data = self.msg_buffer + data if data[-1] != "\n": # All messages from crawl end with \n. # If this one doesn't, it's fragmented. self.msg_buffer = data else: self.msg_buffer = None if self.message_callback: self.message_callback(data) def send_message(self, data): start = datetime.now() try: self.socket.sendto(data, self.crawl_socketpath) except socket.timeout: self.logger.warning("Game socket send timeout", exc_info=True) self.close() return end = datetime.now() if end - start >= timedelta(seconds=1): self.logger.warning("Slow socket send: " + str(end - start)) def close(self): if self.socket: self.io_loop.remove_handler(self.socket.fileno()) self.socket.close() os.remove(self.socketpath) self.socket = None if self.close_callback: self.close_callback()