vnpy--vnpy
170 行
4.6 KiB
Python
170 行
4.6 KiB
Python
import threading
|
|
from time import time
|
|
from functools import lru_cache
|
|
from typing import Any
|
|
|
|
import zmq
|
|
|
|
from .common import HEARTBEAT_TOPIC, HEARTBEAT_TOLERANCE
|
|
|
|
|
|
class RemoteException(Exception):
|
|
"""
|
|
RPC remote exception
|
|
"""
|
|
|
|
def __init__(self, value: Any) -> None:
|
|
"""
|
|
Constructor
|
|
"""
|
|
self._value: Any = value
|
|
|
|
def __str__(self) -> str:
|
|
"""
|
|
Output error message
|
|
"""
|
|
return str(self._value)
|
|
|
|
|
|
class RpcClient:
|
|
""""""
|
|
|
|
def __init__(self) -> None:
|
|
"""Constructor"""
|
|
# zmq port related
|
|
self._context: zmq.Context = zmq.Context()
|
|
|
|
# Request socket (Request–reply pattern)
|
|
self._socket_req: zmq.Socket = self._context.socket(zmq.REQ)
|
|
|
|
# Subscribe socket (Publish–subscribe pattern)
|
|
self._socket_sub: zmq.Socket = self._context.socket(zmq.SUB)
|
|
|
|
# Set socket option to keepalive
|
|
for socket in [self._socket_req, self._socket_sub]:
|
|
socket.setsockopt(zmq.TCP_KEEPALIVE, 1)
|
|
socket.setsockopt(zmq.TCP_KEEPALIVE_IDLE, 60)
|
|
|
|
# Worker thread relate, used to process data pushed from server
|
|
self._active: bool = False # RpcClient status
|
|
self._thread: threading.Thread | None = None # RpcClient thread
|
|
self._lock: threading.Lock = threading.Lock()
|
|
|
|
self._last_received_ping: float = time()
|
|
|
|
@lru_cache(100) # noqa
|
|
def __getattr__(self, name: str) -> Any:
|
|
"""
|
|
Realize remote call function
|
|
"""
|
|
# Perform remote call task
|
|
def dorpc(*args: Any, **kwargs: Any) -> Any:
|
|
# Get timeout value from kwargs, default value is 30 seconds
|
|
timeout: int = kwargs.pop("timeout", 30000)
|
|
|
|
# Generate request
|
|
req: list = [name, args, kwargs]
|
|
|
|
# Send request and wait for response
|
|
with self._lock:
|
|
self._socket_req.send_pyobj(req)
|
|
|
|
# Timeout reached without any data
|
|
n: int = self._socket_req.poll(timeout)
|
|
if not n:
|
|
msg: str = f"Timeout of {timeout}ms reached for {req}"
|
|
raise RemoteException(msg)
|
|
|
|
rep = self._socket_req.recv_pyobj()
|
|
|
|
# Return response if successed; Trigger exception if failed
|
|
if rep[0]:
|
|
return rep[1]
|
|
else:
|
|
raise RemoteException(rep[1])
|
|
|
|
return dorpc
|
|
|
|
def start(
|
|
self,
|
|
req_address: str,
|
|
sub_address: str
|
|
) -> None:
|
|
"""
|
|
Start RpcClient
|
|
"""
|
|
if self._active:
|
|
return
|
|
|
|
# Connect zmq port
|
|
self._socket_req.connect(req_address)
|
|
self._socket_sub.connect(sub_address)
|
|
|
|
# Start RpcClient status
|
|
self._active = True
|
|
|
|
# Start RpcClient thread
|
|
self._thread = threading.Thread(target=self.run)
|
|
self._thread.start()
|
|
|
|
self._last_received_ping = time()
|
|
|
|
def stop(self) -> None:
|
|
"""
|
|
Stop RpcClient
|
|
"""
|
|
if not self._active:
|
|
return
|
|
|
|
# Stop RpcClient status
|
|
self._active = False
|
|
|
|
def join(self) -> None:
|
|
# Wait for RpcClient thread to exit
|
|
if self._thread and self._thread.is_alive():
|
|
self._thread.join()
|
|
self._thread = None
|
|
|
|
def run(self) -> None:
|
|
"""
|
|
Run RpcClient function
|
|
"""
|
|
pull_tolerance: int = HEARTBEAT_TOLERANCE * 1000
|
|
|
|
while self._active:
|
|
if not self._socket_sub.poll(pull_tolerance):
|
|
self.on_disconnected()
|
|
continue
|
|
|
|
# Receive data from subscribe socket
|
|
topic, data = self._socket_sub.recv_pyobj(flags=zmq.NOBLOCK)
|
|
|
|
if topic == HEARTBEAT_TOPIC:
|
|
self._last_received_ping = data
|
|
else:
|
|
# Process data by callable function
|
|
self.callback(topic, data)
|
|
|
|
# Close socket
|
|
self._socket_req.close()
|
|
self._socket_sub.close()
|
|
|
|
def callback(self, topic: str, data: Any) -> None:
|
|
"""
|
|
Callable function
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def subscribe_topic(self, topic: str) -> None:
|
|
"""
|
|
Subscribe data
|
|
"""
|
|
self._socket_sub.setsockopt_string(zmq.SUBSCRIBE, topic)
|
|
|
|
def on_disconnected(self) -> None:
|
|
"""
|
|
Callback when heartbeat is lost.
|
|
"""
|
|
msg: str = f"RpcServer has no response over {HEARTBEAT_TOLERANCE} seconds, please check you connection."
|
|
print(msg)
|