class FakeRpcInterface: @typecheck def __init__(self, cages_list): self._cages_list = cages_list self._queue = InterlockedQueue() self._pass = 0 self._stopped = Event() def get_cages(self): try: self._cages = self._cages_list[self._pass] except IndexError: self._cages = {} self._stopped.set() self._pass += 1 return list(self._cages.keys()) def get_nodes(self, cage): return self._cages[cage] def process_event(self, node, cage, up_down, probe_result): self._queue.push((node, cage, up_down, probe_result)) def extract_events(self): result = {} event = self._queue.pop(1.0) while event is not None: node, cage, up_down, probe_result = event events = result.setdefault("{0:s}.{1:s}".format(node, cage), []) if up_down == "up": events.append(probe_result) else: events.append(None) event = self._queue.pop(1.0) return result
def test_interface_ordering(): file_names = [ "{0:08d}.msg".format(i) for i in range(100) ] shuffle(file_names) for file_name in file_names: write_file(file_name, b"data") loopback_queue = InterlockedQueue() def process_request(request, response): sleep(0.1) loopback_queue.push(request) with active_interface("file_1", **interface_config(process_request = process_request, filename_regex = write_prefix + "[0-9a-f]{8}\\.msg")): for i in range(100): file_name = loopback_queue.pop(3.0)["file_name"] assert os_path.basename(file_name).endswith("{0:08d}.msg".format(i)) if i % 10 == 9: write_file("{0:08d}.msg".format(i // 10), b"data") for i in range(10): file_name = loopback_queue.pop(3.0)["file_name"] assert os_path.basename(file_name).endswith("{0:08d}.msg".format(i)) assert loopback_queue.pop(3.0) is None
def test_process_timeout(): loopback_queue = InterlockedQueue() delay = Event(); delay.set() def process_request(request, response): if delay.is_set(): sleep(pmnc.request.remain + 1.0) loopback_queue.push(request) with active_interface("jms_1", **interface_config(process_request = process_request, request_timeout = 3.0)): fake_request(10.0) xa = pmnc.transaction.create() xa.jms_1.send("<xml/>") message_id = xa.execute()[0] assert loopback_queue.pop(3.0) is None delay.clear() request = loopback_queue.pop(10.0) assert request["message_id"] == message_id assert request["message_text"] == "<xml/>"
def test_send_many(): loopback_queue = InterlockedQueue() def process_request(request, response): loopback_queue.push(int(request["message_text"])) with active_interface("jms_1", **interface_config(process_request = process_request)): for i in range(200): fake_request(30.0) xa = pmnc.transaction.create() xa.jms_1.send(str(i*5)) xa.jms_1.send(str(i*5+1)) xa.jms_1.send(str(i*5+2)) xa.jms_1.send(str(i*5+3)) xa.jms_1.send(str(i*5+4)) xa.execute() received = [] message_number = loopback_queue.pop(10.0) while message_number is not None: received.append(message_number) message_number = loopback_queue.pop(10.0) assert len(received) == 1000 received_sorted = [] for i in range(200): # every 5 should have been sent atomically received_sorted.extend(list(sorted(received[i*5:(i+1)*5]))) assert received_sorted == list(range(1000))
def test_process_failure(): loopback_queue = InterlockedQueue() fail = Event(); fail.set() def process_request(request, response): if fail.is_set(): sleep(1.0) raise Exception("processing failure") else: loopback_queue.push(request) with active_interface("jms_1", **interface_config(process_request = process_request)): fake_request(10.0) xa = pmnc.transaction.create() xa.jms_1.send("<xml/>") message_id = xa.execute()[0] assert loopback_queue.pop(3.0) is None fail.clear() request = loopback_queue.pop(10.0) assert request["message_id"] == message_id assert request["message_text"] == "<xml/>" headers = request["headers"] assert not headers.get("JMSCorrelationID")
def pop(self, queue: InterlockedQueue): # respects wall-time timeout, see issue9892 if self.infinite: return queue.pop() remain = self.remain # there is no special handling for case remain == 0.0, because that while remain > 0.0: # would mean request deadline hence performance is no longer an issue result = queue.pop(remain) if result is not None: return result remain = self.remain else: return None
def pop(self, queue: InterlockedQueue ): # respects wall-time timeout, see issue9892 if self.infinite: return queue.pop() remain = self.remain # there is no special handling for case remain == 0.0, because that while remain > 0.0: # would mean request deadline hence performance is no longer an issue result = queue.pop(remain) if result is not None: return result remain = self.remain else: return None
def test_interface_failure(): loopback_queue = InterlockedQueue() def process_request(request, response): not_defined with active_interface("file_1", **interface_config(process_request = process_request, filename_regex = write_prefix + "[0-9a-f]{8}\\.msg")): assert loopback_queue.pop(3.0) is None file_name = write_file(random_filename() + ".msg", b"data") assert os_path.isfile(file_name) assert loopback_queue.pop(3.0) is None assert os_path.isfile(file_name) remove(file_name)
def test_interface_success(): loopback_queue = InterlockedQueue() def process_request(request, response): loopback_queue.push(request) with active_interface("file_1", **interface_config(process_request = process_request, filename_regex = write_prefix + "[0-9a-f]{8}\\.msg")): assert loopback_queue.pop(3.0) is None file_name = write_file(random_filename() + ".msg", b"request") assert os_path.isfile(file_name) assert loopback_queue.pop(3.0) == dict(file_name = file_name) sleep(3.0) assert not os_path.exists(file_name) assert loopback_queue.pop(3.0) is None
def test_interface_no_wait(): loopback_queue = InterlockedQueue() def process_request(request, response): sleep(5.0) loopback_queue.push(request["invocation_time"]) with active_interface( "schedule_1", **interface_config(process_request=process_request)): dt1 = loopback_queue.pop(9.0) dt2 = loopback_queue.pop(6.0) dt3 = loopback_queue.pop(6.0) assert (dt3 - dt2).seconds == (dt2 - dt1).seconds == 3
def test_interface_success(): loopback_queue = InterlockedQueue() def process_request(request, response): sleep(0.1) loopback_queue.push("ok") with active_interface( "schedule_1", **interface_config(process_request=process_request)): assert loopback_queue.pop(4.0) == "ok" assert loopback_queue.pop(1.0) is None assert loopback_queue.pop(3.0) == "ok" assert loopback_queue.pop(1.0) is None assert loopback_queue.pop(3.0) == "ok"
def drain_queue(): loopback_queue = InterlockedQueue() def process_request(request, response): loopback_queue.push(request) with active_interface("jms_1", **interface_config(process_request = process_request)): while loopback_queue.pop(10.0) is not None: pass
def test_interface_timeout(): loopback_queue = InterlockedQueue() def process_request(request, response): sleep(4.0) loopback_queue.push(request) with active_interface("file_1", **interface_config(process_request = process_request, filename_regex = write_prefix + "[0-9a-f]{8}\\.msg", request_timeout = 3.0)): assert loopback_queue.pop(3.0) is None file_name = write_file(random_filename() + ".msg", b"data") assert os_path.isfile(file_name) assert loopback_queue.pop(3.0) is None assert os_path.isfile(file_name) assert loopback_queue.pop(4.0) == dict(file_name = file_name) assert os_path.isfile(file_name) remove(file_name)
def test_post(): with expected(Exception("the request is no longer pending for response")): pmnc.__getattr__(__name__).post("RQ-ABC", "RESULT") rs_queue = InterlockedQueue() _rs_queues["RQ-ABC"] = rs_queue pmnc.__getattr__(__name__).post("RQ-ABC", "RESULT") assert rs_queue.pop() == "RESULT"
def test_interface_failure(): loopback_queue = InterlockedQueue() def process_request(request, response): not_defined with active_interface( "schedule_1", **interface_config(process_request=process_request)): assert loopback_queue.pop(4.0) is None
def test_post(): with expected( Exception("the request is no longer pending for response")): pmnc.__getattr__(__name__).post("RQ-ABC", "RESULT") rs_queue = InterlockedQueue() _rs_queues["RQ-ABC"] = rs_queue pmnc.__getattr__(__name__).post("RQ-ABC", "RESULT") assert rs_queue.pop() == "RESULT"
def test_too_large(): fake_request(10.0) q = InterlockedQueue() def process_request(request, response): q.push(request["packet"]) with active_interface("udp", **interface_config(process_request = process_request)) as ifc: pmnc.transaction.udp_1.send(b"x" * 60000) assert q.pop(3.0) is None
def test_success(): fake_request(10.0) q = InterlockedQueue() def process_request(request, response): q.push(request["packet"]) with active_interface("udp", **interface_config(process_request = process_request)) as ifc: msg = b"foo" pmnc.transaction.udp_1.send(msg) assert q.pop(3.0) == msg
def test_timeout(): fake_request(10.0) q = InterlockedQueue() def process_request(request, response): sleep(5.0) with active_interface("udp", **interface_config(process_request = process_request)) as ifc: msg = b"foo" pmnc.transaction.udp_1.send(msg) assert q.pop(3.0) is None
def test_deletion_failure(): loopback_queue = InterlockedQueue() def process_request(request, response): loopback_queue.push(request) with active_interface("file_1", **interface_config(process_request = process_request, filename_regex = write_prefix + "[0-9a-f]{8}\\.msg")) as ifc: ifc._remove_file_ = ifc._remove_file ifc._remove_file = lambda file_name: 1 / 0 file_name = write_file(random_filename() + ".msg", b"data") assert loopback_queue.pop(3.0) == dict(file_name = file_name) assert loopback_queue.pop(3.0) is None assert os_path.isfile(file_name) assert ifc._processed_files == { file_name } ifc._remove_file = ifc._remove_file_ assert loopback_queue.pop(3.0) is None assert not os_path.exists(file_name) assert ifc._processed_files == set()
def test_process_one(): loopback_queue = InterlockedQueue() def process_request(request, response): loopback_queue.push(request) with active_interface("jms_1", **interface_config(process_request = process_request)): fake_request(10.0) xa = pmnc.transaction.create() xa.jms_1.send(russian, JMSCorrelationID = russian, FOOBAR = "123") message_id = xa.execute()[0] request = loopback_queue.pop(10.0) assert request["message_id"] == message_id assert request["message_text"] == russian headers = request["headers"] assert headers["JMSCorrelationID"] == russian and headers["FOOBAR"] == "123"
class Interface: # SMPP interface @typecheck def __init__(self, name: str, *, server_address: (str, int), connect_timeout: float, response_timeout: float, ping_interval: optional(float), system_id: str, password: str, system_type: str, esme_ton: byte, esme_npi: byte, esme_addr: str, esme_type: one_of("rcvr", "xmit", "xcvr"), request_timeout: optional(float) = None, **kwargs): # this kwargs allows for extra application-specific # settings in config_interface_smpp_X.py self._name = name self._response_timeout = response_timeout if ping_interval: self._ping_timeout = Timeout(ping_interval) self._ping_response_timeout = Timeout(response_timeout) else: self._ping_timeout = self._ping_response_timeout = None self._ping_request = None self._in_q = InterlockedQueue() self._out_q = InterlockedQueue() self._inflight = InflightRequests() self._ceased = Event() if esme_type == "rcvr": bind_pdu = BindReceiverPDU elif esme_type == "xmit": bind_pdu = BindTransmitterPDU elif esme_type == "xcvr": bind_pdu = BindTransceiverPDU self._create_connection = \ lambda: _SMPPConnection(name, self._in_q, self._out_q, self._inflight, server_address = server_address, connect_timeout = connect_timeout, response_timeout = response_timeout, system_id = system_id, password = password, system_type = system_type, esme_ton = esme_ton, esme_npi = esme_npi, esme_addr = esme_addr, bind_pdu = bind_pdu) self._request_timeout = request_timeout or \ pmnc.config_interfaces.get("request_timeout") # this is now static if pmnc.request.self_test == __name__: # self-test self._process_request = kwargs["process_request"] name = property(lambda self: self._name) ceased = property(lambda self: self._ceased.is_set()) ################################### def start(self): self._maintainer = HeavyThread(target = self._maintainer_proc, name = "{0:s}/mnt".format(self.name)) self._maintainer.start() def cease(self): self._ceased.set() self._maintainer.stop() def stop(self): pass ################################### def _maintainer_proc(self): while not current_thread().stopped(): try: # try to establish a connection, do it infinitely or until the interface is stopped while True: try: self._connection = self._create_connection() self._connection.start() except: pmnc.log.error(exc_string()) failure_timeout = max(self._request_timeout, 30.0) if current_thread().stopped(failure_timeout): return else: break # while True try: while not current_thread().stopped() and not self._connection.failed: # process incoming PDUs req = self._in_q.pop(1.0) if req is not None: self._handle_pdu(req) # if there is an outstanding ping request, check for response if self._ping_request and self._ping_response_timeout.expired: ping_request, self._ping_request = self._ping_request, None _wait_response(ping_request, 0.001) # if it's time to send another ping request, do so if self._ping_timeout and self._ping_timeout.expired: try: self._ping_request = EnquireLinkPDU.create() self._out_q.push(self._ping_request) self._ping_response_timeout.reset() finally: self._ping_timeout.reset() finally: self._connection.stop() except: pmnc.log.error(exc_string()) # log and ignore ################################### # this method processes the request PDUs received by this interface from SMSC def _handle_pdu(self, req): if isinstance(req, EnquireLinkPDU): # respond to pings automatically resp = req.create_response() self._out_q.push(resp) else: # note that this interface does not wait for its requests to complete request = pmnc.interfaces.begin_request( timeout = self._request_timeout, interface = self._name, protocol = "smpp", parameters = dict(auth_tokens = dict()), description = "incoming {0:s}".format(req)) pmnc.interfaces.enqueue(request, self.wu_handle_pdu, (req, ), {}) ################################### @typecheck def wu_handle_pdu(self, req: RequestPDU): try: # see for how long the request was on the execution queue up to this moment # and whether it has expired in the meantime, if it did there is no reason # to proceed and we simply bail out if pmnc.request.expired: pmnc.log.error("request has expired and will not be processed") success = False return # goes through finally section below with pmnc.performance.request_processing(): request = dict(pdu = req) response = dict(pdu = req.create_nack(error_codes.ESME_RUNKNOWNERR)) try: self._process_request(request, response) except: response["pdu"] = req.create_nack(error_codes.ESME_RSYSERR) raise finally: self._out_q.push(response["pdu"]) except: pmnc.log.error(exc_string()) # log and ignore success = False else: success = True finally: # the request ends itself pmnc.interfaces.end_request(success) # possibly way after deadline ################################### def _process_request(self, request, response): handler_module_name = "interface_{0:s}".format(self._name) pmnc.__getattr__(handler_module_name).process_request(request, response) ################################### # this method is called by the coupled resources to send a request PDU to SMSC @typecheck def send(self, req: RequestPDU, timeout: optional(float) = None) -> optional(ResponsePDU): self._out_q.push(req) if timeout is not None: return _wait_response(req, min(timeout, self._response_timeout))
def pop(self, queue: InterlockedQueue ): # respects wall-time timeout, see issue9892 return queue.pop(self.remain) # inherits InterlockedQueue's behaviour
class AdapterHost: @typecheck def __init__(self, class_name: str, *, java: os_path.isfile, arguments: tuple_of(str), classpath: str, jndi: dict_of(str, str), factory: str, queue: str, username: str, password: str): # random line prefix and suffix for packet serialization bol = b2a_hex(urandom(8)).decode("ascii").upper() eol = b2a_hex(urandom(8)).decode("ascii").upper() # compose the executable command line self._args = [ java ] + list(arguments) + \ [ "-classpath", classpath, class_name, "connection.factory={0:s}".format(factory), "connection.queue={0:s}".format(queue), "stdout.bol={0:s}".format(bol), "stdout.eol={0:s}".format(eol) ] if username or password: self._args.append("connection.username={0:s}".format(username)) self._args.append("connection.password={0:s}".format(password)) self._args.extend("jndi.{0:s}={1:s}".format(*t) for t in jndi.items()) self._bol_b = bol.encode("ascii") self._eol_b = eol.encode("ascii") # this set tracks messages that have been processed but # not committed on the server due to a failure self._processed_messages = set() ################################### # adapter input from stdin consists of Packet sequence def _stdin_writer_proc(self, stdin, stdin_queue): try: try: pkt = stdin_queue.pop() while pkt is not None: # this light thread exits upon None in the queue or exception pkt.save_to_stream(stdin, 128) if pkt.get("XPmncResponse") == "COMMIT": message_id = pkt.pop("XPmncMessageID") self._processed_messages.remove(message_id) pkt = stdin_queue.pop() finally: stdin.close() except: pmnc.log.error(exc_string()) # log and ignore ################################### # adapter output from stdout consists of Packet sequence def _stdout_reader_proc(self, stdout, stdin_queue): try: try: try: pkt = Packet.load_from_stream(stdout, self._bol_b, self._eol_b) while pkt is not None: # this light thread exits upon eof or exception self._stdout_queue.push(pkt) pkt = Packet.load_from_stream(stdout, self._bol_b, self._eol_b) finally: stdout.close() finally: stdin_queue.push(None) # this releases the associated stdin writer except: pmnc.log.error(exc_string()) # log and ignore ################################### # adapter output from stderr is discarded (yet it has to be read) def _stderr_reader_proc(self, stderr): try: try: while stderr.read(512): # this light thread exits only upon exception pass finally: stderr.close() except: pmnc.log.error(exc_string()) # log and ignore ################################### # the adapter is considered up until the process exits def _adapter_running(self): return self._adapter.poll() is None ################################### # this method starts the adapter process, creates the handling # threads and waits for the adapter to report its readiness def _start_adapter(self, adapter_usage, adapter_name, start_timeout): pmnc.log.info("starting adapter process for {0:s} {1:s}".\ format(adapter_usage, adapter_name)) if pmnc.log.debug: pmnc.log.debug("adapter process command line: {0:s}".format(" ".join(self._args))) self._adapter = popen(*self._args) # start the java process try: if pmnc.log.debug: pmnc.log.debug("adapter process (pid {0:d}) has started".\ format(self._adapter.pid)) # the process has started and its initialization is underway # create light threads for controlling stdin/out/err, these # threads are not stopped explicitly but exit when the adapter # exits and the pipes break self._stdin_queue = InterlockedQueue() self._stdin_writer = LightThread(target = self._stdin_writer_proc, args = (self._adapter.stdin, self._stdin_queue), name = "{0:s}:stdin".format(adapter_name)) self._stdin_writer.start() self._stdout_queue = InterlockedQueue() self._stdout_reader = LightThread(target = self._stdout_reader_proc, args = (self._adapter.stdout, self._stdin_queue), name = "{0:s}:stdout".format(adapter_name)) self._stdout_reader.start() self._stderr_queue = InterlockedQueue() self._stderr_reader = LightThread(target = self._stderr_reader_proc, args = (self._adapter.stderr, ), name = "{0:s}:stderr".format(adapter_name)) self._stderr_reader.start() # wait for the adapter to come up and report readiness while not start_timeout.expired: pkt = self._stdout_queue.pop(min(0.5, start_timeout.remain)) if pkt is None: # see whether the adapter has exited if not self._adapter_running(): retcode = self._adapter.wait() if retcode is not None: raise Exception("adapter process exited with " "retcode {0:d}".format(retcode)) elif "XPmncError" in pkt: raise Exception(pkt["XPmncError"]) elif pkt.get("XPmncStatus") == "READY": break else: raise Exception("adapter process returned invalid status") else: raise Exception("timeout waiting for adapter process to initialize") except: self._stop_adapter(start_timeout) # what remained of start timeout is used for stopping raise pmnc.log.info("adapter process (pid {0:d}) is ready".format(self._adapter.pid)) ################################### # if not exited peacefully, adapter has to be killed def _stop_adapter(self, stop_timeout): while self._adapter_running() and not stop_timeout.expired: sleep(min(0.5, stop_timeout.remain)) if self._adapter_running(): pmnc.log.warning("killing runaway adapter process " "(pid {0:d})".format(self._adapter.pid)) self._adapter.kill() else: pmnc.log.info("adapter process (pid {0:d}) exited with retcode {1:d}".\ format(self._adapter.pid, self._adapter.wait()))
def pop(self, queue: InterlockedQueue): # respects wall-time timeout, see issue9892 return queue.pop(self.remain) # inherits InterlockedQueue's behaviour