# auto generated by update_py.py

import json
import os
import signal
import threading
import time
from collections import defaultdict, deque

import zmq

import tlclient.linker.message_comm as message
from tlclient.linker.constant import (ANY_FIST_NAME, DEFAULT_REQ_ID, MASTER_FIST_NAME,
                             IS_NEW_VERSION,
                             CommType, FistType, HeartBeatStatus, MsgType,
                             SubscribeTopic)
from tlclient.linker.frame import Frame, FrameHeaderStatus
from tlclient.linker.logger import Logger
from tlclient.linker.structs import FistAddrUpdate, FistInfo
from tlclient.linker.timer import Timer
from tlclient.linker.utility import bytify
from tlclient.linker.event import Event, PBEvent
from tlclient.linker.batch import MemBuffer, PBMemBuffer
import tlclient.linker.frame_pb2 as frame_pb2
import tlclient.linker.comm_pb2 as comm_pb2 


class Fist:

    BATCH_BUFFER_SIZE = 102400
    POLL_WAIT_TIME = 1000
    REQ_WAIT_TIME = 2000

    def __init__(self, fist_name, fist_type, env_name, addr=None):
        self.logger = Logger.get_logger(fist_name)
        self.logger.info(f'[cons of fist] (is new version){IS_NEW_VERSION}')
        self.fist_name = fist_name
        self.fist_type = fist_type
        self.env_name = env_name
        self.router_name = None
        self._context = zmq.Context()
        # internal
        self.source_id = None
        self.pub_sock = None
        self.pull_sock = None
        self.rep_sock = None
        self.sub_master_sock = None
        self.master_addr = None
        self.influx_db_client = None
        self.sub_poller = zmq.Poller()
        self.buffer_size = 0
        self.push_socks = {}
        self.req_socks = {}
        self.req_sock_locks = defaultdict(threading.RLock)
        self.fist_req_addrs = {}
        self.cmd_lock = threading.Lock()
        self.cmd_pending_rids = set()
        self.received_signal = None
        self.heart_beat_msg = message.MsgHeartBeat()
        self.heart_beat_msg.fist_type = self.fist_type
        self.heart_beat_msg.fist_name = bytify(self.fist_name)
        self.heart_beat_msg.desc_name = bytify('')
        self.heart_beat_msg.hb_status = HeartBeatStatus.NOT_AVAILABLE
        self.pub_buffer: MemBuffer = None  # buffer used when batch
        self.pb_pub_buffer: PBMemBuffer = None
        self.push_buffer: MemBuffer = None  # buffer used when batch
        self.pb_push_buffer: PBMemBuffer = None
        # thread ready
        self.sub_ready = False
        self.pull_ready = False
        self.rep_ready = False
        self.sub_master_ready = False
        if addr:
            self.set_master_addr(addr)
        # auto reg
        self._auto_sub_fist_types = set()
        self._auto_req_fist_types = set()
        # init mutex
        self.push_mutex = threading.Lock()
        self.use_seperate_queue = True
        self.push_q = deque()
        # use push_mutex as the underlying lock
        self.push_c = threading.Condition(self.push_mutex)

    def is_stopped(self):
        return self.received_signal is not None

    def set_master_addr(self, addr):
        self.master_addr = addr

    def create_fist(self):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_REQ_FIST_CREATE)
        f.set_nano(Timer.nano())
        req = comm_pb2.ReqFistCreate()
        req.fist_name = self.fist_name
        req.fist_type = self.fist_type
        req.env_name = self.env_name
        req.pid = os.getpid()
        f.set_data(req)
        self.reg_req_master()
        ret = self.req_master(f)
        ret_obj = ret.get_obj(comm_pb2.RspFistCreate)
        if ret_obj.source <= 0:
            raise Exception("failed to create fist: {}".format(ret_obj.err_msg))
        self.source_id = ret_obj.source
        # no has_xxx method here in ret_obj
        self.use_separate_queue = ret_obj.use_separate_queue
        self.logger.debug(f'[create_fist] (source_id){self.source_id} (use_separate_queue){self.use_separate_queue}')
        self.reg_sub_master()

    def set_io_thread_num(self, io_threads=1):
        self._context.setsockopt(zmq.IO_THREADS, io_threads)

    def set_buffer_size(self, buffer_size):
        assert isinstance(buffer_size, int) and buffer_size >= 0, 'buffer size should be a non-negative integer'
        self.buffer_size = buffer_size

    def set_pub(self):
        addr = self.get_bind_addr(CommType.Zmq_PUB)
        if not IS_NEW_VERSION:
            socket_type = zmq.PUB
        else:
            socket_type = zmq.ROUTER
        self.pub_sock = self._context.socket(socket_type)
        self.pub_sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        #self.pub_sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        self.pub_sock.bind(addr)
        self.logger.debug('[set_pub] (addr){}'.format(addr))
        return True

    def set_pull(self):
        addr = self.get_bind_addr(CommType.Zmq_PULL)
        self.pull_sock = self._context.socket(zmq.SUB)
        self.pull_sock.setsockopt(zmq.SUBSCRIBE, b'')
        #self.pull_sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        self.pull_sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        self.pull_sock.bind(addr)
        self.logger.debug('[set_pull] (addr){}'.format(addr))
        return True

    def set_rep(self):
        addr = self.get_bind_addr(CommType.Zmq_REP)
        self.rep_sock = self._context.socket(zmq.REP)
        self.rep_sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        self.rep_sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        self.rep_sock.bind(addr)
        self.logger.debug('[set_rep] (addr){}'.format(addr))
        return True

    def reg_sub(self, other_fist_name, topics=None):
        addr = self.get_connect_addr(other_fist_name, CommType.Zmq_PUB)
        if not IS_NEW_VERSION:
            sub_sock = self._context.socket(zmq.SUB)
            # set subscriptions
            topics = topics or SubscribeTopic.get_default_topic_set(self.fist_type)
            if SubscribeTopic.NOT_AVAILABLE not in topics and SubscribeTopic.UNEXPECTED not in topics:
                self.logger.warn('[reg_sub] the "unexpt" topic is NOT subscribed')
            for topic in topics:
                topic_prefix = SubscribeTopic.get_topic_prefix(topic)
                sub_sock.setsockopt(zmq.SUBSCRIBE, topic_prefix)
                self.logger.debug('[reg_sub] subscribed topic (prefix){}'.format(topic_prefix))
        else:
            sub_sock = self._context.socket(zmq.DEALER)
            # routing_id: fist_name + ' ' + fist_type
            routing_id = self.fist_name + ' ' + str(FistType.parse(self.fist_type))
            sub_sock.setsockopt(zmq.ROUTING_ID, bytify(routing_id))
            self.logger.debug(f'[reg_sub] routing (id){routing_id}')
        # set HWM
        #sub_sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        sub_sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        # to connect
        sub_sock.connect(addr)
        self.sub_poller.register(sub_sock, zmq.POLLIN)
        self.logger.debug('[reg_sub] (other_fist_name){} (addr){}'.format(other_fist_name, addr))
        return True

    def reg_sub_master(self):
        addr = self.get_connect_addr(MASTER_FIST_NAME, CommType.Zmq_PUB)
        if not IS_NEW_VERSION:
            self.sub_master_sock = self._context.socket(zmq.SUB)
            self.sub_master_sock.setsockopt(zmq.SUBSCRIBE, b'')
        else:
            self.sub_master_sock = self._context.socket(zmq.DEALER)
            # routing_id: fist_name + ' ' + fist_type
            routing_id = self.fist_name + ' ' + str(FistType.parse(self.fist_type))
            self.sub_master_sock.setsockopt(zmq.ROUTING_ID, bytify(routing_id))
            self.logger.debug(f'[reg_sub_master] routing (id){routing_id}')
        #self.sub_master_sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        self.sub_master_sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        self.sub_master_sock.connect(addr)
        self.logger.debug('[reg_sub_master] (addr){}'.format(addr))
        return True

    def reg_push(self, other_fist_name):
        addr = self.get_connect_addr(other_fist_name, CommType.Zmq_PULL)
        push_sock = self._context.socket(zmq.PUB)
        push_sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        #push_sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        push_sock.connect(addr)
        self.push_socks[other_fist_name] = push_sock
        self.logger.debug('[reg_push] (other_fist_name){} (addr){}'.format(other_fist_name, addr))
        return True

    def _reg_req(self, other_fist_name, addr):
        sock = self._context.socket(zmq.REQ)
        if not IS_NEW_VERSION:
            if other_fist_name == MASTER_FIST_NAME:
                sock.setsockopt(zmq.ROUTING_ID, bytify(self.fist_name))
        else:
            # routing_id: fist_name + ' ' + fist_type
            routing_id = self.fist_name + ' ' + str(FistType.parse(self.fist_type))
            sock.setsockopt(zmq.ROUTING_ID, bytify(routing_id))
        sock.setsockopt(zmq.SNDHWM, self.buffer_size)
        sock.setsockopt(zmq.RCVHWM, self.buffer_size)
        sock.connect(addr)
        self.req_socks[other_fist_name] = sock
        self.logger.debug('[reg_req] (target){} (addr){}'.format(other_fist_name, addr))
        return True

    def reg_req(self, other_fist_name):
        addr = self.get_connect_addr(other_fist_name, CommType.Zmq_REP)
        self.fist_req_addrs[other_fist_name] = addr
        return self._reg_req(other_fist_name, addr)

    def reg_req_master(self):
        self.fist_req_addrs[MASTER_FIST_NAME] = self.master_addr
        return self._reg_req(MASTER_FIST_NAME, self.master_addr)

    # auto

    def _req_fist_infos(self):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_GET_STATUS)
        f.set_source(self.get_source_id())
        f.set_nano(Timer.nano())
        rsp_f = self.req_master(f)
        fist_infos = json.loads(rsp_f.get_string())
        return [FistInfo(**info) for info in fist_infos]

    def auto_reg_sub(self, fist_type: int):
        fist_infos = self._req_fist_infos()
        for info in fist_infos:
            if info.fist_type == fist_type:
                self.reg_sub(info.fist_name)
        self._auto_sub_fist_types.add(fist_type)

    def auto_reg_req(self, fist_type: int):
        fist_infos = self._req_fist_infos()
        for info in fist_infos:
            if info.fist_type == fist_type:
                self.reg_req(info.fist_name)
        self._auto_req_fist_types.add(fist_type)

    def pub_f(self, frame):
        self.pub_sock.send(frame.buf)

    def batch_send_frame(self, mem_buffer: MemBuffer, pb_buffer: PBMemBuffer, sock, frame_deque):
        if len(frame_deque) == 0:
            return 0
        mem_buffer.init()
        pb_buffer.init()
        frame_num = 0
        idx = MemBuffer.HEADER_LENGTH
        while len(frame_deque) != 0:
            f = frame_deque[0]
            if isinstance(f, Frame):
                if idx + frame_deque[0].get_length() >= mem_buffer.get_length():
                    break
                if mem_buffer.get_frame_num() == 0:
                    mem_buffer.set_subscribe_topic(f.get_subscribe_topic())
                elif mem_buffer.get_subscribe_topic() != f.get_subscribe_topic():
                    break
                idx = mem_buffer.append(idx, f)
            else:
                if pb_buffer.empty():
                    pb_buffer.set_content_id(f.get_content_id())
                elif pb_buffer.get_content_id() != f.get_content_id():
                    break
                pb_buffer.append(f)
            frame_num += 1
            frame_deque.popleft()
        if frame_num == 0:
            # must be Frame
            f = frame_deque.popleft()
            self.logger.warning('[batch_zmq_pub] a big frame is sent (size){}'.format(f.get_length()))
            sock.send(f.buf)
            return 1
        else:
            if mem_buffer.get_frame_num() != 0:
                mem_buffer.finalize()
                sock.send(mem_buffer._buffer[:idx])
            if not pb_buffer.empty():
                sock.send(pb_buffer.buf)
            return frame_num

    def batch_pub(self, frame_deque):
        if IS_NEW_VERSION:
            raise NotImplementedError('pub in new version is not implemented')

        if self.pub_buffer is None:
            self.pub_buffer = MemBuffer(length=self.BATCH_BUFFER_SIZE)
        if self.pb_pub_buffer is None:
            self.pb_pub_buffer = PBMemBuffer()
        return self.batch_send_frame(self.pub_buffer, self.pb_pub_buffer, self.pub_sock, frame_deque)

    def batch_push(self, fist_name, frame_deque):
        ps_sock = self.push_socks[fist_name]
        if self.push_buffer is None:
            self.push_buffer = MemBuffer(length=self.BATCH_BUFFER_SIZE)
        if self.pb_push_buffer is None:
            self.pb_push_buffer = PBMemBuffer()
        return self.batch_send_frame(self.push_buffer, self.pb_push_buffer, ps_sock, frame_deque)

    def pub(self, obj, msg_type, req_id, err_id=0, topic=SubscribeTopic.UNEXPECTED):
        if IS_NEW_VERSION:
            raise NotImplementedError('pub in new version is not implemented')

        if isinstance(msg_type, MsgType):
            f = Frame()
        else:
            f = Event.new_pb_frame()
        f.set_status(FrameHeaderStatus.NORMAL)
        f.set_msg_type(msg_type)
        f.set_req_id(req_id)
        f.set_err_id(err_id)
        f.set_nano(Timer.nano())
        f.set_source(self.get_source_id())
        f.set_data(obj)
        f.set_subscribe_topic(topic)
        self.pub_f(f)

    def push(self, fist_name, obj, msg_type, req_id, err_id=0):
        ps_sock = self.push_socks[fist_name]
        if isinstance(msg_type, MsgType):
            f = Frame()
        else:
            f = Event.new_pb_frame()
        f.set_status(FrameHeaderStatus.NORMAL)
        f.set_msg_type(msg_type)
        f.set_req_id(req_id)
        f.set_err_id(err_id)
        f.set_nano(Timer.nano())
        f.set_source(self.get_source_id())
        f.set_data(obj)
        ps_sock.send(f.buf)

    def _req(self, fist_name, frame, retries=1):
        with self.req_sock_locks[fist_name]:
            sock = self.req_socks[fist_name]
            try:
                sock.send(frame.buf)
            except zmq.error.ZMQError as e:
                # double req problem, reset req socket
                if e.errno == 156384763 and retries > 0:
                    self.logger.warning('[req] invalid sock state, to recreate sock and resend frame (mt){}'.format(frame.get_msg_type()))
                    self._reg_req(fist_name, self.fist_req_addrs[fist_name])
                    retries -= 1
                    return self._req(fist_name, frame, retries=retries)
                # reraise all other errs
                else:
                    raise e

            # recv data
            poll = zmq.Poller()
            poll.register(sock, zmq.POLLIN)
            sockets = dict(poll.poll(Fist.REQ_WAIT_TIME))
            if sock in sockets:
                return sock.recv()
            else:
                self.logger.error('[req] failed to recv data (mt){}'.format(frame.get_msg_type()))
                return None

    def req(self, fist_name, obj, msg_type, req_id, err_id=0):
        if isinstance(msg_type, MsgType):
            f = Frame()
        else:
            f = Event.new_pb_frame()
        f.set_status(FrameHeaderStatus.NORMAL)
        f.set_msg_type(msg_type)
        f.set_req_id(req_id)
        f.set_err_id(err_id)
        f.set_nano(Timer.nano())
        f.set_source(self.get_source_id())
        f.set_data(obj)
        ret = self._req(fist_name, f)
        if ret is None:
            raise Exception("Timeout in req {}!".format(fist_name))
        e = Event.new_event(ret)
        if e is None:
            raise Exception(f"wrong format msg received in req! (type){ret[0]}")
        return e

    def req_master(self, frame: PBEvent):
        ret = self._req(MASTER_FIST_NAME, frame)
        if ret is None:
            raise Exception("Timeout in req master!")
        e = Event.new_event(ret)
        if e is None:
            raise Exception(f"wrong format msg received in req master! (type){ret[0]}")
        return e

    def notify(self, title, content, notification_type):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_REQ_NOTIFY)
        f.set_source(self.get_source_id())
        f.set_nano(Timer.nano())
        req = comm_pb2.ReqNotify()
        req.title = title
        req.message = content
        req.type = notification_type
        f.set_data(req)
        ret = self.req_master(f)
        if ret is None:
            self.logger.error('[notify] timeout (rsp){}'.format(ret))
            return False
        rsp_obj = ret.get_obj(comm_pb2.RspNotify)
        if not rsp_obj.accepted:
            self.logger.error('[notify] failed (err_msg){}'.format(rsp_obj.err_msg))
        return rsp_obj.accepted

    def send_req_command(self, target_fist_name, content):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_CMD_REQUEST)
        f.set_source(self.get_source_id())
        f.set_nano(Timer.nano())
        req = message.ReqCommand()
        req.request_id = -1
        req.target_fist_name = target_fist_name
        req.from_fist_name = self.fist_name
        req.content = content
        f.set_string(json.dumps(req.to_dict()))
        with self.cmd_lock:
            ret = self.req_master(f)
            if not ret:
                self.logger.error('[cmd] failed')
                return -1
            else:
                rid = ret.get_req_id()
                self.cmd_pending_rids.add(rid)
                return rid

    def send_rsp_command(self, request_id, content):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_CMD_RESPONSE)
        f.set_source(self.get_source_id())
        f.set_nano(Timer.nano())
        rsp = message.RspCommand()
        rsp.request_id = request_id
        rsp.fist_name = self.fist_name
        rsp.content = content
        f.set_string(json.dumps(rsp.to_dict()))
        ret = self.req_master(f)
        return ret != None

    def on_pub_frame(self, f):
        pass

    def on_push_frame(self, f):
        pass

    def on_req_frame(self, f):
        return Frame()

    def on_req_command(self, request_id, from_fist_name, content):
        pass

    def on_rsp_command(self, request_id, from_fist_name, content):
        pass

    def on_pub_master(self, f):
        msg_type = f.get_msg_type()
        if msg_type == frame_pb2.MsgType.MSG_TYPE_CMD_SUICIDE:
            suicide_req = f.get_obj(comm_pb2.ReqFistSuicide)
            fist_name = suicide_req.fist_name
            if fist_name in [ANY_FIST_NAME, self.fist_name]:
                self.stop()
        elif msg_type == frame_pb2.MsgType.MSG_TYPE_CMD_REQUEST:
            s = f.get_string()
            req = message.ReqCommand(json.loads(s))
            if req.target_fist_name in [ANY_FIST_NAME, self.fist_name]:
                self.on_req_command(req.request_id, req.from_fist_name, req.content)
        elif msg_type == frame_pb2.MsgType.MSG_TYPE_CMD_RESPONSE:
            s = f.get_string()
            rsp = message.RspCommand(json.loads(s))
            with self.cmd_lock:
                if rsp.request_id in self.cmd_pending_rids:
                    self.cmd_pending_rids.discard(rsp.request_id)
                    self.on_rsp_command(rsp.request_id, rsp.fist_name, rsp.content)
        elif msg_type == frame_pb2.MsgType.MSG_TYPE_FIST_ADDR_UPDATE:
            info = FistAddrUpdate(**json.loads(f.get_string()))
            if info.comm_type == comm_pb2.CommType.COMM_TYPE_ZMQ_PUB and \
                    info.fist_type in self._auto_sub_fist_types:
                self.reg_sub(info.fist_name)
                self.logger.info('[master_pub] auto reg sub (name){} (type){}'.format(
                    info.fist_name, FistType.read(info.fist_type)
                ))
            elif info.comm_type == comm_pb2.CommType.COMM_TYPE_ZMQ_REP and \
                    info.fist_type in self._auto_req_fist_types:
                self.reg_req(info.fist_name)
                self.logger.info('[master_pub] auto reg req (name){} (type){}'.format(
                    info.fist_name, FistType.read(info.fist_type)
                ))

    def on_close(self):
        pass

    def start(self):
        signal.signal(signal.SIGTERM, self.signal_handler)
        signal.signal(signal.SIGINT, self.signal_handler)

        if self.use_seperate_queue:
            t = threading.Thread(target=self.consume_push)
            t.setDaemon(True)
            t.start()

        self.sub_ready = False
        self.pull_ready = False
        self.rep_ready = False
        self.sub_master_ready = False
        ts = [
            threading.Thread(target=self.run_sub),
            threading.Thread(target=self.run_pull),
            threading.Thread(target=self.run_rep),
            threading.Thread(target=self.run_sub_master),
        ]
        for t in ts:
            t.setDaemon(True)
            t.start()
        while not (self.sub_ready and self.pull_ready and self.rep_ready and self.sub_master_ready):
            time.sleep(0.01)

    def stop(self, signum=signal.SIGTERM):
        self.received_signal = signum
        self.on_close()
        Logger.stop_async_handlers()

    def join(self):
        while True:
            time.sleep(0.01)
            if self.is_stopped():
                self.logger.debug('[main_thread] ended (sig){}'.format(self.received_signal))
                break

    def set_hb_status(self, status=HeartBeatStatus.NOT_AVAILABLE):
        self.heart_beat_msg.hb_status = status

    def start_heart_beat(self, fist_to_push, sec_interval=5):
        ts = threading.Thread(target=self._keep_heart_beat, args=[fist_to_push, sec_interval])
        ts.setDaemon(True)
        ts.start()

    def _keep_heart_beat(self, fist_to_push, sec_interval):
        self.logger.info(f'[hb] started! (f){fist_to_push} (r_name){self.router_name} (sec){sec_interval}')

        # can not push to 'fist_to_push', use safe_push will always push to router
        self.heart_beat_msg.router_name = bytify(self.router_name)
        while not self.is_stopped():
            self.safe_push(self.heart_beat_msg, MsgType.FIST_HEART_BEAT, DEFAULT_REQ_ID)
            time.sleep(sec_interval)

    def signal_handler(self, signum=None, frame=None):
        if self.received_signal is None:
            self.stop(signum=signum)

    def get_source_id(self):
        return self.source_id

    ######################
    # internal functions #
    ######################
    def get_bind_addr(self, comm_type):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_REQ_FIST_SET)
        f.set_source(self.get_source_id())
        f.set_nano(Timer.nano())
        req = comm_pb2.ReqFistSet()
        req.comm_type = comm_type
        f.set_data(req)
        ret = self.req_master(f)
        rsp_obj = ret.get_obj(comm_pb2.RspFistSet)
        if rsp_obj.is_allowed:
            return rsp_obj.addr
        else:
            raise Exception("Rejected: (fist_name){} (comm_type){} (err_msg){}".format(self.fist_name, comm_type, rsp_obj.err_msg))

    def get_connect_addr(self, fist_name, comm_type):
        f = Event.new_pb_frame()
        f.set_msg_type(frame_pb2.MsgType.MSG_TYPE_REQ_FIST_REG)
        f.set_source(self.get_source_id())
        f.set_nano(Timer.nano())
        req = comm_pb2.ReqFistReg()
        req.fist_name = fist_name
        req.comm_type = comm_type
        f.set_data(req)
        ret = self.req_master(f)
        rsp_obj = ret.get_obj(comm_pb2.RspFistReg)
        # WARNING!! DO NOT use the addr if it's not connectable
        if not rsp_obj.input_registered or not rsp_obj.input_connectable:
            self.logger.warning('[get_connect_addr] rejected by master (fist_name){} (comm_type){} (reged){} (connectable){}'.format(fist_name, comm_type, rsp_obj.input_registered, rsp_obj.input_connectable))
            return None
        else:
            return rsp_obj.addr

    def run_sub(self):
        self.sub_ready = True
        while not self.is_stopped():
            try:
                if not self.sub_poller.sockets:
                    time.sleep(1)
                    continue

                socks = dict(self.sub_poller.poll(Fist.POLL_WAIT_TIME))
                for sock in socks:
                    if not IS_NEW_VERSION:
                        ret = sock.recv_multipart()[-1]
                    else:
                        ret = sock.recv()
                    e = Event.new_event(ret)
                    if e is not None:
                        for f in e.frames():
                            self.on_pub_frame(f)
            except Exception as e:
                self.logger.exception('run_sub failed!')
                raise e
        self.logger.debug('[sub_thread] ended (sig){}'.format(self.received_signal))

    def run_sub_master(self):
        self.sub_master_ready = True
        if self.sub_master_sock:
            while not self.is_stopped():
                try:
                    ret = self.sub_master_sock.recv_multipart()[-1]
                    e = Event.new_event(ret)
                    if e is not None:
                        for f in e.frames():
                            self.on_pub_master(f)
                except Exception as e:
                    self.logger.exception('run_sub_master failed!')
                    raise e
            self.logger.debug('[sub_master_thread] ended (sig){}'.format(self.received_signal))

    def run_pull(self):
        self.pull_ready = True
        if self.pull_sock:
            while not self.is_stopped():
                try:
                    ret = self.pull_sock.recv()
                    e = Event.new_event(ret)
                    if e is not None:
                        for f in e.frames():
                            self.on_push_frame(f)
                except Exception as e:
                    self.logger.exception('run_pull failed!')
                    raise e
            self.logger.debug('[pull_thread] ended (sig){}'.format(self.received_signal))

    def run_rep(self):
        self.rep_ready = True
        if self.rep_sock:
            while not self.is_stopped():
                try:
                    ret = self.rep_sock.recv()
                    e = Event.new_event(ret)
                    if e is not None:
                        for f in e.frames():
                            rsp_f = self.on_req_frame(f)
                            self.rep_sock.send(rsp_f.buf)
                except Exception as e:
                    self.logger.exception('run_rep failed!')
                    raise e
            self.logger.debug('[rep_thread] ended (sig){}'.format(self.received_signal))

    # db setting
    def set_influxdb(self, host='localhost', port=8086, user='', passwd='', db='traders_link'):
        from influxdb import InfluxDBClient
        self.influx_db_client = InfluxDBClient(host, port, user, passwd, db)

    def write_points(self, points):
        if self.influx_db_client is None:
            return False
        if not isinstance(points, list):
            points = [points]
        self.logger.debug('[write_points] (points){}'.format(points))
        return self.influx_db_client.write_points(points)

    def query(self, sql):
        if self.influx_db_client is None:
            return None
        return self.influx_db_client.query(sql)

    def safe_push(self, obj, msg_type, req_id=0):
        if self.use_seperate_queue:
            if isinstance(msg_type, MsgType):
                f = Frame()
            else:
                f = Event.new_pb_frame()
            f.set_status(FrameHeaderStatus.NORMAL)
            f.set_msg_type(msg_type)
            f.set_req_id(req_id)
            f.set_err_id(0)
            f.set_nano(Timer.nano())
            f.set_source(self.get_source_id())
            f.set_data(obj)
            # acquire lock in push_c and release it automatically
            with self.push_c:
                self.push_q.append(f)
                self.push_c.notify()
        else:
            with self.push_mutex:
                self.push(self.router_name, obj, msg_type, req_id)

    def consume_push(self):
        self.logger.debug('{} consume push started!'.format(self.fist_name))
        while not self.is_stopped():
            # must hold lock to call wait_for
            with self.push_c:
                # wait for that push_q is not empty and reacquire lock automatically
                self.push_c.wait_for(lambda :len(self.push_q) != 0)
                self.batch_push(self.router_name, self.push_q)
