gaterpc


Namegaterpc JSON
Version 0.2.11 PyPI version JSON
download
home_pagehttps://github.com/firejoke/gate-rpc
SummaryA RPC software based on ZeroMQ with built-in Majordomo.
upload_time2024-11-04 07:24:00
maintainerNone
docs_urlNone
authorShi Fan
requires_python>=3.9
licenseBSD 3-Clause License Copyright (c) 2024, shifan All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
keywords zero zeromq rpc
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            gate-rpc
############

使用 ZeroMQ 和 asyncio 开发的“gate-rpc”。

- 使用 msgpack 序列化消息
- 支持在线程池和进程池里运行同步函数
- 当函数返回的是 Generator 或 AsyncGenerator 时,会换成流式传输,接收端通过遍历 StreamReply 实例即可获得
- 如果预计返回值会很大,可以使用 HugeData 包装,会压缩返回值并分块流式传输,默认块大小通过 Settings 的 HUGE_DATA_SIZEOF 来设置
- 使用队列传输和记录日志,避免因为日志导致事件循环被阻塞
- 使用 Settings 实例管理全局配置,通过 Settings.configure 配置值,在每次实例化 Worker、Server、AMajordomo、Client 各类之前,使用 Settings.setup 初始化配置(例如日志配置)
- Gate 类可以自组集群,可转发当前节点无法处理的请求

安装
******

可以直接使用 pip 安装,或直接下载源码随意放置。

::

  pip install gaterpc

配置
******

在实例化 Worker、Service、AMajordomo、Client、Gate 等各类之前,
需要运行 Settings.setup 函数来初始化全局配置 [#f1]_ 。
当日志使用 utils.AQueueHandler 类,并且 listener 指向的是一个使用 asyncio 的异步队列时,
Settings.setup 方法要在协程里调用,因为 setup 方法内会初始化日志配置。

::

  # 除了直接配置 Settings 的属性以外,也可以创建一个包含配置项的 python 文件
  # 然后在程序入口配置 Settings.USER_SETTINGS
  project/
  ├── __init__.py
  ├── package
  │   ├── __init__..py
  │   ├── settings.py
  ├── settings.py
  ├── run.py
  # 在 run.py 里
  Settings.xxxx = yyyy
  Settings.USER_SETTINGS = "package.settings or settings"
  Settings.setup()

全局配置里有些是使用 utils.LazyAttribute 构建的描述器,在设置和获取时可能会触发校验或被修改。

 例如像是 RUN_PATH 和 LOG_PATH 这类路径的配置,如果没有配置值,而只配置了 BASE_PATH ,
 那么在调用这类属性时,会直接在 BASE_PATH 后拼接,并创建出真实目录,
 而在配置该类属性时,则会触发类型检查,保证配置的是 pathlib.Path 类的实例。

自定义配置也可以使用 utils.LazyAttribute 包装成描述器,以便不用立即配置,而是在调用时才配置。

LazyAttribute 初始化时接受三个可选参数:

- raw: 原始值,默认是 empty
- render: 一个 Callable 对象,必须接受两个位置参数,在实例调用该描述器所表示的属性时,会传递调用实例和原始值,并将返回值返回给调用实例
- process: 一个 Callable 对象,必须接受两个位置参数,在实例设置该描述器所表示的属性时,会传递调用实例和原始值,并将返回值保存为该描述器的原始值


::

    import os
    from pathlib import Path
    from gaterpc.utils import LazyAttribute, empty, TypeValidator, ensure_mkdir


    BASE_PATH = Path("xxx")
    NAME = os.getenv("NAME", "MyGATE")
    # 使用实例已经有的值来配置
    ZAP_PLAIN_DEFAULT_USER = LazyAttribute(
        render=lambda instance, p: instance.NAME if p is empty else p
    )
    # 在实例设置属性时对值进行校验
    A_PATH = LazyAttribute(
        render=lambda instance, p: ensure_mkdir(
            instance.BASE_PATH.joinpath("a")
        ) if p is empty else ensure_mkdir(p),
        process=lambda instance, p: TypeValidator(Path)(p)
    )
    # 通过在实例设置属性时抛出异常来阻止设置该属性
    B_PATH = LazyAttribute(
        render=lambda instance, p: ensure_mkdir(
            instance.A_PATH.joinpath("b")
        ),
        process=lambda instance, p: (_ for _ in ()).throw(AttributeError)
    )


以下是可能修改的一些主要配置的解释

::

  - ZAP_ADDR: str = ZAP 服务绑定的地址,如果是和代理服务一起使用,最好使用 ipc 类型,且不要和代理使用同一个 Context
  - ZAP_REPLY_TIMEOUT: float = 等待 ZAP 服务的回复的超时时间,单位是秒,远比普通的 REPLY_TIMEOUT 短,因为 zap 服务处理每一个 zap 请求必须很快
  - ZMQ_CONTEXT: dict = ZMQ 的 CONTEXT 的参数配置
  - ZMQ_SOCK: dict = ZMQ 的 SOCKET 的参数配置(CURVE 相关的配置可以在这里配置,也可以在 AMajordomo 的 bind_frontend 和 bind_backend 方法的 sock_opt 参数里传递)
  - HEARTBEAT: int = 全局的默认的心跳间隔,单位是毫秒
  - TIMEOUT: float = 全局的默认超时时间,单位是秒
  - MESSAGE_MAX: int = Worker 和 Client 实例里等待处理的消息最大数量
  - HUGE_DATA_SIZEOF: int = 每次传输的结果值的最大大小,超过该值的将会被压缩并分片传输
  - HUGE_DATA_COMPRESS_MODULE: str = 使用的压缩模块的名称 [#f1]_
  - SERVICE_DEFAULT_NAME: str = 默认的服务名,当在实例化 Service 时如果不提供 name 参数则会以这个为服务名
  - WORKER_ADDR: str = Majordomo 用于绑定给 Worker 连接的地址,一般是 inproc 类型,当使用 inproc 地址时,需要和 Majordomo 使用同一个 Context
  - MDP_INTERNAL_SERVICE_PREFIX: bytes = MDP 内部服务的前缀
  - MDP_HEARTBEAT_INTERVAL: int = 服务端和客户端相对于中间代理的心跳间隔时间,单位是毫秒,默认是使用上面的 HEARTBEAT
  - MDP_HEARTBEAT_LIVENESS: int = 心跳活跃度,用于判定掉线的丢失心跳次数,即当超过该次数*心跳时间没有收到心跳则认为已经掉线,默认3次
  - REPLY_TIMEOUT: float = 客户端调用远程方法时,等待回复的超时时间,应设置的远远大于心跳时间,默认是一分钟
  - STREAM_REPLY_MAXSIZE: int = 流式数据使用的缓存队列的最大长度(使用的 asyncio.Queue)
  - REPLY_TIMEOUT: float = 获取回复的超时时间,也是流式传输的每一个子回复的超时时间,单位是秒,默认使用的全局的 TIMEOUT
  - GATE_CLUSTER_NAME: str = gate 集群的集群名
  - GATE_CLUSTER_DESCRIPTION: str = gate 集群的描述
  - GATE_VERSION: str = "01" Gate 集群协议版本
  - GATE_MEMBER: bytes = b"GATE01" 集群的成员版本
  - GATE_IP_VERSION: int = 4 监听 ip4 地址还是 ip6 地址
  - GATE_MULTICAST_GROUP: str = Gate 的多播地址
  - GATE_MULTICAST_PORT: str = Gate 的多播端口
  - GATE_MULTICAST_TTL: int = ipv4 的多播的TTL
  - GATE_MULTICAST_HOP_LIMIT: int = ipv6 的多播跳数
  - GATE_CURVE_PUBKEY: bytes = GATE 节点间连接加密的私钥
  - GATE_CURVE_KEY: bytes = GATE 节点间连接加密公钥

特殊返回值的序列化通过 MessagePack 的全局实例 gaterpc.utils.message_pack 来定制 [#f2]_ 。

::

    from gaterpc.utils import message_pack
    message_pack.prepare_pack = 在使用 msgpack.packb 时,传递给 default 参数的可执行对象
    message_pack.unpack_object_hook = 在使用 msgpack.unpackb 时,传递给 object_hook 的可执行对象
    message_pack.unpack_object_pairs_hook = 在使用 msgpack.unpackb 时,传递给 object_pairs_hook 的可执行对象
    message_pack.unpack_object_list_hook = 在使用 msgpack.unpackb 时,传递给 list_hook 的可执行对象


.. rubric:: Footnotes

.. [#f1] Settings.HUGE_DATA_COMPRESS_MODULE 除了内置的 gzip,bz2,lzma,还可以使用外部模块,只要模块提供 compressor 和 decompressor 方法即可,
   compressor 需要返回一个带有 compress 方法的增量压缩器对象,decompressor 需要返回一个带有 decompress 的增量解压缩器对象

.. [#f2] 单一返回值和生成器的元素返回值,以及巨型返回值都会使用 utils.msg_pack 和 utils.msg_unpack 来序列化和反序列化,
   这两个方法内部是使用的 utils.MessagePack 的全局实例,如果不能返回常规的“字符串”,“列表”,“字典”的返回值,建议配置这几个配置。

测试示范
********

实例化 ZAP 服务后,需要配置校验策略。

::

    zap = AsyncZAPService()
    zap.configure_plain(
        Settings.ZAP_DEFAULT_DOMAIN,
        {
            Settings.ZAP_PLAIN_DEFAULT_USER: Settings.ZAP_PLAIN_DEFAULT_PASSWORD
        }
    )
    zap.start()

继承 Worker 类,用 interface 装饰希望被远程调用的方法,
然后实例化一个 Server 来创建 Worker 的实例,这个 worker 实例的描述信息由 server 实例提供。

::

    from gaterpc.core import Context, Worker
    from gaterpc.utils import interface

    # Worker
    class GRWorker(Worker):
        @interface
        async def atest(self, *args, **kwargs):
            loop = self._get_loop()
            return {
                "name": "async atest",
                "args": args,
                "kwargs": kwargs,
                "loop_time": loop.time()
            }

        @interface("process"):
            cpu_bound()

        @interface("thread")
        def test(self, *args, **kwargs):
            return {
                "name": "test",
                "args": args,
                "kwargs": kwargs,
                "loop_time": time()
            }

        @interface
        def test_generator(self, maximum: int):
            i = 0
            while i < maximum:
                yield i
                i += 1

        @interface
        async def test_agenerator(self, maximum: int):
            i = 0
            while i < maximum:
                await asyncio.sleep(0.1)
                yield i
                i += 1

    async def test():
        Settings.setup()
        ctx = Context()
        gr = Service(name="SRkv")
        gr_worker = gr.create_worker(
            GRWorker, "inproc://gate.worker.01",
            context=ctx,
            zap_mechanism=Settings.ZAP_MECHANISM_PLAIN,
            zap_credentials=(
                Settings.ZAP_PLAIN_DEFAULT_USER,
                Settings.ZAP_PLAIN_DEFAULT_PASSWORD
            )
        )
        gr_worker.run()

当要执行 IO 密集或 CPU 密集型操作时,可以通过 interface 装饰器指定是否使用在执行器里运行,
也可以不在 interface 指定,而是在方法内使用 run_in_executor,以便可以使用自定义的执行器。

另外,所有同步的函数都会使用默认执行器执行,默认执行器是 ThreadPoolExecutor 实例,可以修改。

如果连接地址使用的 inproc 类型,一定要和 Majordomo 使用同一个 Context。

::

    @interface("thread")
    async def test_io():
        return result

    @interface
    async def test_io():
        result = await self.run_in_executor(self.thread_executor, func, *args, **kwargs)
        return result

    @interface
    async def test_cpu():
        # 如果需要和 CPU 密集型执行器里的方法交换数据,
        # 可以使用 utils 模块内定义的全局代理管理器 SyncManager 来创建代理对象使用。
        queue = SyncManager.Queue()
        result = await self.run_in_executor(self.process_executor, func, queue, *args, **kwargs)
        return result

实例化代理时要绑定两个地址,一个用于给后端服务连接上来,一个给前端客户端连接上来。

也可以只绑定后端地址,将代理实例作为前端使用,适合不长期自动运行的任务(参见test/testMajordomo.py)。
还可以只绑定前端地址,将代理实例作为后端使用,适合简单的rpc调用,使用 interface 装饰器来定义对外接口。

::

  from gaterpc.core import AMajordomo, Context
  from gaterpc.utils import interface


  class AM(Amajordomo):
      # 所有内部处理程序都必须能接收关键词参数
      # 位置参数可以自定义,也可以没有,关键词参数会被更新加入固定参数
      # kwargs.update({
      #    "__client_id": client_id,
      #    "__request_id": request_id,
      # })
      @interface
      def internal_func(self, *args, **kwargs):
          status_code = b"200" # response code
          result = Any
          return status_code, result

  Settings.setup()
  ctx = Context()
  majordomo = AM(
      context=ctx,
      gate_zap_mechanism=Settings.ZAP_MECHANISM_PLAIN,
      gate_zap_credentials=(
          Settings.ZAP_PLAIN_DEFAULT_USER,
          Settings.ZAP_PLAIN_DEFAULT_PASSWORD
      )
  )
  # 绑定后端地址,为空则使用 Settings.WORKER_ADDR,sock_opt 可选关键字参数用来定制 socket,比如 CURVE 配置
  majordomo.bind_backend()
  majordomo.bind_frontend("ipc:///tmp/gate-rpc/run/c1")
  # 如果启用了 zap 服务
  await majordomo.connect_zap(zap_addr=zipc)
  # 发起 zap 请求和等待 zap 处理结果是使用的 asyncio.Future 来处理异步等待,
  # 并且使用 LRUCache 缓存每个地址使用不同的校验策略的结果,避免频繁发起验证请求而导致增加 rpc 调用的时间
  majordomo.run()

客户端直接连接代理地址,使用点语法调用远程方法,一般格式是 client.服务名.方法名,当直接使用 client.方法名时,会使用默认服务名调用。

::

    # Client
    Settings.setup()
    gr_cli = Client(
        zap_mechanism=Settings.ZAP_MECHANISM_PLAIN,
        zap_credentials=(
            Settings.ZAP_PLAIN_DEFAULT_USER,
            Settings.ZAP_PLAIN_DEFAULT_PASSWORD
        )
    )
    gr_cli.connect(check_socket_addr(frontend_addr))
    await gr_cli.GateRPC.test("a", "b", "c", time=time())
    await gr_cli.GateRPC.atest("a", "b", "c", time=time())
    async for i in await gr_cli.SRkv.test_agenerator(10):
        print(i)
    await gr_cli.test_huge_data()

客户端调用的远程方法后,会创建一个延迟回调用来删掉缓存的已经执行完毕的请求,包括超时没拿到回复的请求,
而流式回复会每次回调时都检查一次该 StreamReply 实例是否已经结束,没结束就再创建一个延迟回调后续再检查。

更详细的测试用例可以看看test目录下的测试脚本

Gate cluster
************

当布置多代理集群时,用 bind_gate 绑定集群节点地址。

Gate 节点默认开启多播用于发现其他节点,多播地址通过 Settings 或单独的个性化配置来配置。

Gate 集群内不同 Gate 节点可以承载不同的业务服务(Service),
而整个 Gate 集群可以提供所有 Gate 节点承载的服务,
在 Gate 集群内各个节点会转发当前节点的前端请求到任何提供该服务的其他节点。

Gate 节点可以请求其他节点的内部方法(比如分布式算法的集群节点选举),
例如可以通过内部方法,来扩展分布式应用。

Gate 节点可以在整个集群内发送通知,发送者只发不管,接收者只收不回,通知处理程序在初始化后,
手动使用 self.register_event_handler 来注册对应通知的处理程序,所有处理程序都必须是协程。

Gate 节点默认有注册一个 UpdateActiveService 通知事件

::

  class AG(Gate):
      def __init__(
          self,
          port: int,
          *,
          identity: str = None,
          context: Context = None,
          heartbeat: int = None,
          multicast: bool = True,
          cluster_name: str = None,
          gate_zap_mechanism: Union[str, bytes] = None,
          gate_zap_credentials: tuple = None,
          thread_executor: ThreadPoolExecutor = None,
          process_executor: ProcessPoolExecutor = None
      ):
          super.__init__(......)
          self.register_event_handler("AX", self.ax_event_handler)

  async def ax_event_handler(self, gate: RemoteGate, *body):
      # 通知处理程序的参数只有两个,一个发送通知的 RemoteGate 实例,一个包含多个消息通知内容
      pass


笔记
******

客户端的请求和回复的异步处理是通过创建 asyncio.Future ,并使用 asyncio.wait_for 超时等待。

::

    # 请求远程方法
    request_id = await Client._request(service_name, func_name, args, kwargs)
    response = await asyncio.wait_for(Client.replies[request_id], timeout=Client.reply_timeout)
    # 接收回复
    await Client.replies[request_id].set_result(body)

如果自定义方法的返回对象的大小无法预估会有多大,建议用 HugeData 包装后再返回

::

    # data 必须要是 bytes ,会通过 SharedMemory 或 os.pipe 来传递给压缩器或解压缩器
    hd = HugeData(
        Settings.HUGE_DATA_END_TAG,
        Settings.HUGE_DATA_EXCEPT_TAG,
        data=data, compress_module="gzip", compress_level=9, blksize=1000
    )
    c_d = b""
    async for _d in hd.compress():
        c_d += _d
    # 或者不提供 data ,HugeData 初始化时会创建一个 os.pipe 的管道,然后通过 add_data 追加需要处理的数据
    hd = HugeData(
        Settings.HUGE_DATA_END_TAG,
        Settings.HUGE_DATA_EXCEPT_TAG,
        compress_module="gzip", compress_level=9, blksize=1000,
        timeout=Settings.TIMEOUT
    )
    d = process_data()
    # 可以整个直接丢进去
    hd.add_data(d)
    # 或者分块传递
    for i in range(0, len(d), 1000):
        _d = d[i: i + 1000]
        hd.add_data(_d)
    # 数据添加完毕后,务必调用一下flush方法
    hd.flush()
    d_d = b""
    # 传递未处理数据和接收已处理数据可以异步执行
    async for _d in hd.decompress(1000):
        d_d += _d

HugeData 的 compress 和 decompress 方法都会在进程池里执行增量压缩和增量解压缩,
返回的异步生成器每次获取的字节数大小可以通过初始化 HugeData 时传递 blksize 来限制,
compress 方法对每一块返回的大小的限制是 HugeData 内部实现,
decompress 方法对每一块返回的大小限制则是由压缩模块来实现,
会在调用解压缩器实例的 decompress 方法时传递一个 max_length 位置参数。


可以给 zmq.sock 配置 CURVE 加密,可以通过 Settings 进行全局配置,
也可以在使用 bind 和 connect 方法时,通过 sock_opt 传递

::

  # 在客户端和服务端
  curve_dir = Path(__file__).parent.joinpath("curvekey/")
  if curve_dir.exists():
      g_public, _ = zmq.auth.load_certificate(
          curve_dir.joinpath("gate.key")
      )
      cw_public, cw_secret = zmq.auth.load_certificate(
          curve_dir.joinpath("cw.key_secret")
      )
  if cw_secret:
        Settings.ZMQ_SOCK.update(
            {
                z_const.CURVE_SECRETKEY: cw_secret,
                z_const.CURVE_PUBLICKEY: cw_public,
                z_const.CURVE_SERVERKEY: g_public,
            }
        )

  # 在代理端
  curve_dir = Path(__file__).parent.joinpath("curvekey/")
  if curve_dir.exists():
      g_public, g_secret = zmq.auth.load_certificate(
          curve_dir.joinpath("gate.key_secret")
      )
      cw_public, _ = zmq.auth.load_certificate(
          curve_dir.joinpath("cw.key")
      )
  else:
      g_public = g_secret = b""
      cw_public = b""
  if g_secret:
        gr_majordomo.bind_backend(
            sock_opt={
                z_const.CURVE_SECRETKEY: g_secret,
                z_const.CURVE_PUBLICKEY: g_public,
                z_const.CURVE_SERVER: True,
            }
        )


在用到 Gate 集群时,不建议使用 Settings.ZMQ_SOCK 全局配置,

<del>`因为 Gate 集群的节点互联是使用的单一 ROUTE 套接字,绑定和连接都是同一个 ROUTE 套接字`</del>

Gate 集群节点间互联,会单独使用一个 DEALER 套接字连接其他节点的 ROUTE 套接字

在使用由 gaterpc.utils.AQueueHandler 做为处理器的日志处理器时,
要避免跨越线程和跨事件循环实例来记录日志,
在将 StreamHandler 作为 AQueueHandler 的 handler_class 参数时会就遇到跨事件循环调用的错误

要注意并发太多时,对套接字类型的选择和缓冲区的配置,同时适时的让出 io,
可以适当提高 Settings 里的 ZMQ_SOCk 配置里的 z_const.HWM,并且提高系统的默认缓冲区大小;

目前在WSL-AlmaLinux release 8.9上使用 ipc 协议测得100000次回显,全部请求发送用时20秒左右,
包含收到最后一个回复总用时29秒左右。

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/firejoke/gate-rpc",
    "name": "gaterpc",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "zero, zeroMQ, rpc",
    "author": "Shi Fan",
    "author_email": "Shi Fan <firejokeshi@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/c5/97/436873d3482c32577500d96200a1808796d8c667f477348104893d6b7d40/gaterpc-0.2.11.tar.gz",
    "platform": null,
    "description": "gate-rpc\n############\n\n\u4f7f\u7528 ZeroMQ \u548c asyncio \u5f00\u53d1\u7684\u201cgate-rpc\u201d\u3002\n\n- \u4f7f\u7528 msgpack \u5e8f\u5217\u5316\u6d88\u606f\n- \u652f\u6301\u5728\u7ebf\u7a0b\u6c60\u548c\u8fdb\u7a0b\u6c60\u91cc\u8fd0\u884c\u540c\u6b65\u51fd\u6570\n- \u5f53\u51fd\u6570\u8fd4\u56de\u7684\u662f Generator \u6216 AsyncGenerator \u65f6\uff0c\u4f1a\u6362\u6210\u6d41\u5f0f\u4f20\u8f93\uff0c\u63a5\u6536\u7aef\u901a\u8fc7\u904d\u5386 StreamReply \u5b9e\u4f8b\u5373\u53ef\u83b7\u5f97\n- \u5982\u679c\u9884\u8ba1\u8fd4\u56de\u503c\u4f1a\u5f88\u5927\uff0c\u53ef\u4ee5\u4f7f\u7528 HugeData \u5305\u88c5\uff0c\u4f1a\u538b\u7f29\u8fd4\u56de\u503c\u5e76\u5206\u5757\u6d41\u5f0f\u4f20\u8f93\uff0c\u9ed8\u8ba4\u5757\u5927\u5c0f\u901a\u8fc7 Settings \u7684 HUGE_DATA_SIZEOF \u6765\u8bbe\u7f6e\n- \u4f7f\u7528\u961f\u5217\u4f20\u8f93\u548c\u8bb0\u5f55\u65e5\u5fd7\uff0c\u907f\u514d\u56e0\u4e3a\u65e5\u5fd7\u5bfc\u81f4\u4e8b\u4ef6\u5faa\u73af\u88ab\u963b\u585e\n- \u4f7f\u7528 Settings \u5b9e\u4f8b\u7ba1\u7406\u5168\u5c40\u914d\u7f6e\uff0c\u901a\u8fc7 Settings.configure \u914d\u7f6e\u503c\uff0c\u5728\u6bcf\u6b21\u5b9e\u4f8b\u5316 Worker\u3001Server\u3001AMajordomo\u3001Client \u5404\u7c7b\u4e4b\u524d\uff0c\u4f7f\u7528 Settings.setup \u521d\u59cb\u5316\u914d\u7f6e\uff08\u4f8b\u5982\u65e5\u5fd7\u914d\u7f6e\uff09\n- Gate \u7c7b\u53ef\u4ee5\u81ea\u7ec4\u96c6\u7fa4\uff0c\u53ef\u8f6c\u53d1\u5f53\u524d\u8282\u70b9\u65e0\u6cd5\u5904\u7406\u7684\u8bf7\u6c42\n\n\u5b89\u88c5\n******\n\n\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528 pip \u5b89\u88c5\uff0c\u6216\u76f4\u63a5\u4e0b\u8f7d\u6e90\u7801\u968f\u610f\u653e\u7f6e\u3002\n\n::\n\n  pip install gaterpc\n\n\u914d\u7f6e\n******\n\n\u5728\u5b9e\u4f8b\u5316 Worker\u3001Service\u3001AMajordomo\u3001Client\u3001Gate \u7b49\u5404\u7c7b\u4e4b\u524d\uff0c\n\u9700\u8981\u8fd0\u884c Settings.setup \u51fd\u6570\u6765\u521d\u59cb\u5316\u5168\u5c40\u914d\u7f6e [#f1]_ \u3002\n\u5f53\u65e5\u5fd7\u4f7f\u7528 utils.AQueueHandler \u7c7b\uff0c\u5e76\u4e14 listener \u6307\u5411\u7684\u662f\u4e00\u4e2a\u4f7f\u7528 asyncio \u7684\u5f02\u6b65\u961f\u5217\u65f6\uff0c\nSettings.setup \u65b9\u6cd5\u8981\u5728\u534f\u7a0b\u91cc\u8c03\u7528\uff0c\u56e0\u4e3a setup \u65b9\u6cd5\u5185\u4f1a\u521d\u59cb\u5316\u65e5\u5fd7\u914d\u7f6e\u3002\n\n::\n\n  # \u9664\u4e86\u76f4\u63a5\u914d\u7f6e Settings \u7684\u5c5e\u6027\u4ee5\u5916\uff0c\u4e5f\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a\u5305\u542b\u914d\u7f6e\u9879\u7684 python \u6587\u4ef6\n  # \u7136\u540e\u5728\u7a0b\u5e8f\u5165\u53e3\u914d\u7f6e Settings.USER_SETTINGS\n  project/\n  \u251c\u2500\u2500 __init__.py\n  \u251c\u2500\u2500 package\n  \u2502   \u251c\u2500\u2500 __init__..py\n  \u2502   \u251c\u2500\u2500 settings.py\n  \u251c\u2500\u2500 settings.py\n  \u251c\u2500\u2500 run.py\n  # \u5728 run.py \u91cc\n  Settings.xxxx = yyyy\n  Settings.USER_SETTINGS = \"package.settings or settings\"\n  Settings.setup()\n\n\u5168\u5c40\u914d\u7f6e\u91cc\u6709\u4e9b\u662f\u4f7f\u7528 utils.LazyAttribute \u6784\u5efa\u7684\u63cf\u8ff0\u5668\uff0c\u5728\u8bbe\u7f6e\u548c\u83b7\u53d6\u65f6\u53ef\u80fd\u4f1a\u89e6\u53d1\u6821\u9a8c\u6216\u88ab\u4fee\u6539\u3002\n\n \u4f8b\u5982\u50cf\u662f RUN_PATH \u548c LOG_PATH \u8fd9\u7c7b\u8def\u5f84\u7684\u914d\u7f6e\uff0c\u5982\u679c\u6ca1\u6709\u914d\u7f6e\u503c\uff0c\u800c\u53ea\u914d\u7f6e\u4e86 BASE_PATH \uff0c\n \u90a3\u4e48\u5728\u8c03\u7528\u8fd9\u7c7b\u5c5e\u6027\u65f6\uff0c\u4f1a\u76f4\u63a5\u5728 BASE_PATH \u540e\u62fc\u63a5\uff0c\u5e76\u521b\u5efa\u51fa\u771f\u5b9e\u76ee\u5f55\uff0c\n \u800c\u5728\u914d\u7f6e\u8be5\u7c7b\u5c5e\u6027\u65f6\uff0c\u5219\u4f1a\u89e6\u53d1\u7c7b\u578b\u68c0\u67e5\uff0c\u4fdd\u8bc1\u914d\u7f6e\u7684\u662f pathlib.Path \u7c7b\u7684\u5b9e\u4f8b\u3002\n\n\u81ea\u5b9a\u4e49\u914d\u7f6e\u4e5f\u53ef\u4ee5\u4f7f\u7528 utils.LazyAttribute \u5305\u88c5\u6210\u63cf\u8ff0\u5668\uff0c\u4ee5\u4fbf\u4e0d\u7528\u7acb\u5373\u914d\u7f6e\uff0c\u800c\u662f\u5728\u8c03\u7528\u65f6\u624d\u914d\u7f6e\u3002\n\nLazyAttribute \u521d\u59cb\u5316\u65f6\u63a5\u53d7\u4e09\u4e2a\u53ef\u9009\u53c2\u6570\uff1a\n\n- raw: \u539f\u59cb\u503c\uff0c\u9ed8\u8ba4\u662f empty\n- render: \u4e00\u4e2a Callable \u5bf9\u8c61\uff0c\u5fc5\u987b\u63a5\u53d7\u4e24\u4e2a\u4f4d\u7f6e\u53c2\u6570\uff0c\u5728\u5b9e\u4f8b\u8c03\u7528\u8be5\u63cf\u8ff0\u5668\u6240\u8868\u793a\u7684\u5c5e\u6027\u65f6\uff0c\u4f1a\u4f20\u9012\u8c03\u7528\u5b9e\u4f8b\u548c\u539f\u59cb\u503c\uff0c\u5e76\u5c06\u8fd4\u56de\u503c\u8fd4\u56de\u7ed9\u8c03\u7528\u5b9e\u4f8b\n- process: \u4e00\u4e2a Callable \u5bf9\u8c61\uff0c\u5fc5\u987b\u63a5\u53d7\u4e24\u4e2a\u4f4d\u7f6e\u53c2\u6570\uff0c\u5728\u5b9e\u4f8b\u8bbe\u7f6e\u8be5\u63cf\u8ff0\u5668\u6240\u8868\u793a\u7684\u5c5e\u6027\u65f6\uff0c\u4f1a\u4f20\u9012\u8c03\u7528\u5b9e\u4f8b\u548c\u539f\u59cb\u503c\uff0c\u5e76\u5c06\u8fd4\u56de\u503c\u4fdd\u5b58\u4e3a\u8be5\u63cf\u8ff0\u5668\u7684\u539f\u59cb\u503c\n\n\n::\n\n    import os\n    from pathlib import Path\n    from gaterpc.utils import LazyAttribute, empty, TypeValidator, ensure_mkdir\n\n\n    BASE_PATH = Path(\"xxx\")\n    NAME = os.getenv(\"NAME\", \"MyGATE\")\n    # \u4f7f\u7528\u5b9e\u4f8b\u5df2\u7ecf\u6709\u7684\u503c\u6765\u914d\u7f6e\n    ZAP_PLAIN_DEFAULT_USER = LazyAttribute(\n        render=lambda instance, p: instance.NAME if p is empty else p\n    )\n    # \u5728\u5b9e\u4f8b\u8bbe\u7f6e\u5c5e\u6027\u65f6\u5bf9\u503c\u8fdb\u884c\u6821\u9a8c\n    A_PATH = LazyAttribute(\n        render=lambda instance, p: ensure_mkdir(\n            instance.BASE_PATH.joinpath(\"a\")\n        ) if p is empty else ensure_mkdir(p),\n        process=lambda instance, p: TypeValidator(Path)(p)\n    )\n    # \u901a\u8fc7\u5728\u5b9e\u4f8b\u8bbe\u7f6e\u5c5e\u6027\u65f6\u629b\u51fa\u5f02\u5e38\u6765\u963b\u6b62\u8bbe\u7f6e\u8be5\u5c5e\u6027\n    B_PATH = LazyAttribute(\n        render=lambda instance, p: ensure_mkdir(\n            instance.A_PATH.joinpath(\"b\")\n        ),\n        process=lambda instance, p: (_ for _ in ()).throw(AttributeError)\n    )\n\n\n\u4ee5\u4e0b\u662f\u53ef\u80fd\u4fee\u6539\u7684\u4e00\u4e9b\u4e3b\u8981\u914d\u7f6e\u7684\u89e3\u91ca\n\n::\n\n  - ZAP_ADDR: str = ZAP \u670d\u52a1\u7ed1\u5b9a\u7684\u5730\u5740\uff0c\u5982\u679c\u662f\u548c\u4ee3\u7406\u670d\u52a1\u4e00\u8d77\u4f7f\u7528\uff0c\u6700\u597d\u4f7f\u7528 ipc \u7c7b\u578b\uff0c\u4e14\u4e0d\u8981\u548c\u4ee3\u7406\u4f7f\u7528\u540c\u4e00\u4e2a Context\n  - ZAP_REPLY_TIMEOUT: float = \u7b49\u5f85 ZAP \u670d\u52a1\u7684\u56de\u590d\u7684\u8d85\u65f6\u65f6\u95f4\uff0c\u5355\u4f4d\u662f\u79d2\uff0c\u8fdc\u6bd4\u666e\u901a\u7684 REPLY_TIMEOUT \u77ed\uff0c\u56e0\u4e3a zap \u670d\u52a1\u5904\u7406\u6bcf\u4e00\u4e2a zap \u8bf7\u6c42\u5fc5\u987b\u5f88\u5feb\n  - ZMQ_CONTEXT: dict = ZMQ \u7684 CONTEXT \u7684\u53c2\u6570\u914d\u7f6e\n  - ZMQ_SOCK: dict = ZMQ \u7684 SOCKET \u7684\u53c2\u6570\u914d\u7f6e\uff08CURVE \u76f8\u5173\u7684\u914d\u7f6e\u53ef\u4ee5\u5728\u8fd9\u91cc\u914d\u7f6e\uff0c\u4e5f\u53ef\u4ee5\u5728 AMajordomo \u7684 bind_frontend \u548c bind_backend \u65b9\u6cd5\u7684 sock_opt \u53c2\u6570\u91cc\u4f20\u9012\uff09\n  - HEARTBEAT: int = \u5168\u5c40\u7684\u9ed8\u8ba4\u7684\u5fc3\u8df3\u95f4\u9694\uff0c\u5355\u4f4d\u662f\u6beb\u79d2\n  - TIMEOUT: float = \u5168\u5c40\u7684\u9ed8\u8ba4\u8d85\u65f6\u65f6\u95f4\uff0c\u5355\u4f4d\u662f\u79d2\n  - MESSAGE_MAX: int = Worker \u548c Client \u5b9e\u4f8b\u91cc\u7b49\u5f85\u5904\u7406\u7684\u6d88\u606f\u6700\u5927\u6570\u91cf\n  - HUGE_DATA_SIZEOF: int = \u6bcf\u6b21\u4f20\u8f93\u7684\u7ed3\u679c\u503c\u7684\u6700\u5927\u5927\u5c0f\uff0c\u8d85\u8fc7\u8be5\u503c\u7684\u5c06\u4f1a\u88ab\u538b\u7f29\u5e76\u5206\u7247\u4f20\u8f93\n  - HUGE_DATA_COMPRESS_MODULE: str = \u4f7f\u7528\u7684\u538b\u7f29\u6a21\u5757\u7684\u540d\u79f0 [#f1]_\n  - SERVICE_DEFAULT_NAME: str = \u9ed8\u8ba4\u7684\u670d\u52a1\u540d\uff0c\u5f53\u5728\u5b9e\u4f8b\u5316 Service \u65f6\u5982\u679c\u4e0d\u63d0\u4f9b name \u53c2\u6570\u5219\u4f1a\u4ee5\u8fd9\u4e2a\u4e3a\u670d\u52a1\u540d\n  - WORKER_ADDR: str = Majordomo \u7528\u4e8e\u7ed1\u5b9a\u7ed9 Worker \u8fde\u63a5\u7684\u5730\u5740\uff0c\u4e00\u822c\u662f inproc \u7c7b\u578b\uff0c\u5f53\u4f7f\u7528 inproc \u5730\u5740\u65f6\uff0c\u9700\u8981\u548c Majordomo \u4f7f\u7528\u540c\u4e00\u4e2a Context\n  - MDP_INTERNAL_SERVICE_PREFIX: bytes = MDP \u5185\u90e8\u670d\u52a1\u7684\u524d\u7f00\n  - MDP_HEARTBEAT_INTERVAL: int = \u670d\u52a1\u7aef\u548c\u5ba2\u6237\u7aef\u76f8\u5bf9\u4e8e\u4e2d\u95f4\u4ee3\u7406\u7684\u5fc3\u8df3\u95f4\u9694\u65f6\u95f4\uff0c\u5355\u4f4d\u662f\u6beb\u79d2\uff0c\u9ed8\u8ba4\u662f\u4f7f\u7528\u4e0a\u9762\u7684 HEARTBEAT\n  - MDP_HEARTBEAT_LIVENESS: int = \u5fc3\u8df3\u6d3b\u8dc3\u5ea6\uff0c\u7528\u4e8e\u5224\u5b9a\u6389\u7ebf\u7684\u4e22\u5931\u5fc3\u8df3\u6b21\u6570\uff0c\u5373\u5f53\u8d85\u8fc7\u8be5\u6b21\u6570*\u5fc3\u8df3\u65f6\u95f4\u6ca1\u6709\u6536\u5230\u5fc3\u8df3\u5219\u8ba4\u4e3a\u5df2\u7ecf\u6389\u7ebf\uff0c\u9ed8\u8ba43\u6b21\n  - REPLY_TIMEOUT: float = \u5ba2\u6237\u7aef\u8c03\u7528\u8fdc\u7a0b\u65b9\u6cd5\u65f6\uff0c\u7b49\u5f85\u56de\u590d\u7684\u8d85\u65f6\u65f6\u95f4\uff0c\u5e94\u8bbe\u7f6e\u7684\u8fdc\u8fdc\u5927\u4e8e\u5fc3\u8df3\u65f6\u95f4\uff0c\u9ed8\u8ba4\u662f\u4e00\u5206\u949f\n  - STREAM_REPLY_MAXSIZE: int = \u6d41\u5f0f\u6570\u636e\u4f7f\u7528\u7684\u7f13\u5b58\u961f\u5217\u7684\u6700\u5927\u957f\u5ea6\uff08\u4f7f\u7528\u7684 asyncio.Queue\uff09\n  - REPLY_TIMEOUT: float = \u83b7\u53d6\u56de\u590d\u7684\u8d85\u65f6\u65f6\u95f4\uff0c\u4e5f\u662f\u6d41\u5f0f\u4f20\u8f93\u7684\u6bcf\u4e00\u4e2a\u5b50\u56de\u590d\u7684\u8d85\u65f6\u65f6\u95f4\uff0c\u5355\u4f4d\u662f\u79d2\uff0c\u9ed8\u8ba4\u4f7f\u7528\u7684\u5168\u5c40\u7684 TIMEOUT\n  - GATE_CLUSTER_NAME: str = gate \u96c6\u7fa4\u7684\u96c6\u7fa4\u540d\n  - GATE_CLUSTER_DESCRIPTION: str = gate \u96c6\u7fa4\u7684\u63cf\u8ff0\n  - GATE_VERSION: str = \"01\" Gate \u96c6\u7fa4\u534f\u8bae\u7248\u672c\n  - GATE_MEMBER: bytes = b\"GATE01\" \u96c6\u7fa4\u7684\u6210\u5458\u7248\u672c\n  - GATE_IP_VERSION: int = 4 \u76d1\u542c ip4 \u5730\u5740\u8fd8\u662f ip6 \u5730\u5740\n  - GATE_MULTICAST_GROUP: str = Gate \u7684\u591a\u64ad\u5730\u5740\n  - GATE_MULTICAST_PORT: str = Gate \u7684\u591a\u64ad\u7aef\u53e3\n  - GATE_MULTICAST_TTL: int = ipv4 \u7684\u591a\u64ad\u7684TTL\n  - GATE_MULTICAST_HOP_LIMIT: int = ipv6 \u7684\u591a\u64ad\u8df3\u6570\n  - GATE_CURVE_PUBKEY: bytes = GATE \u8282\u70b9\u95f4\u8fde\u63a5\u52a0\u5bc6\u7684\u79c1\u94a5\n  - GATE_CURVE_KEY: bytes = GATE \u8282\u70b9\u95f4\u8fde\u63a5\u52a0\u5bc6\u516c\u94a5\n\n\u7279\u6b8a\u8fd4\u56de\u503c\u7684\u5e8f\u5217\u5316\u901a\u8fc7 MessagePack \u7684\u5168\u5c40\u5b9e\u4f8b gaterpc.utils.message_pack \u6765\u5b9a\u5236 [#f2]_ \u3002\n\n::\n\n    from gaterpc.utils import message_pack\n    message_pack.prepare_pack = \u5728\u4f7f\u7528 msgpack.packb \u65f6\uff0c\u4f20\u9012\u7ed9 default \u53c2\u6570\u7684\u53ef\u6267\u884c\u5bf9\u8c61\n    message_pack.unpack_object_hook = \u5728\u4f7f\u7528 msgpack.unpackb \u65f6\uff0c\u4f20\u9012\u7ed9 object_hook \u7684\u53ef\u6267\u884c\u5bf9\u8c61\n    message_pack.unpack_object_pairs_hook = \u5728\u4f7f\u7528 msgpack.unpackb \u65f6\uff0c\u4f20\u9012\u7ed9 object_pairs_hook \u7684\u53ef\u6267\u884c\u5bf9\u8c61\n    message_pack.unpack_object_list_hook = \u5728\u4f7f\u7528 msgpack.unpackb \u65f6\uff0c\u4f20\u9012\u7ed9 list_hook \u7684\u53ef\u6267\u884c\u5bf9\u8c61\n\n\n.. rubric:: Footnotes\n\n.. [#f1] Settings.HUGE_DATA_COMPRESS_MODULE \u9664\u4e86\u5185\u7f6e\u7684 gzip\uff0cbz2\uff0clzma\uff0c\u8fd8\u53ef\u4ee5\u4f7f\u7528\u5916\u90e8\u6a21\u5757\uff0c\u53ea\u8981\u6a21\u5757\u63d0\u4f9b compressor \u548c decompressor \u65b9\u6cd5\u5373\u53ef\uff0c\n   compressor \u9700\u8981\u8fd4\u56de\u4e00\u4e2a\u5e26\u6709 compress \u65b9\u6cd5\u7684\u589e\u91cf\u538b\u7f29\u5668\u5bf9\u8c61\uff0cdecompressor \u9700\u8981\u8fd4\u56de\u4e00\u4e2a\u5e26\u6709 decompress \u7684\u589e\u91cf\u89e3\u538b\u7f29\u5668\u5bf9\u8c61\n\n.. [#f2] \u5355\u4e00\u8fd4\u56de\u503c\u548c\u751f\u6210\u5668\u7684\u5143\u7d20\u8fd4\u56de\u503c\uff0c\u4ee5\u53ca\u5de8\u578b\u8fd4\u56de\u503c\u90fd\u4f1a\u4f7f\u7528 utils.msg_pack \u548c utils.msg_unpack \u6765\u5e8f\u5217\u5316\u548c\u53cd\u5e8f\u5217\u5316\uff0c\n   \u8fd9\u4e24\u4e2a\u65b9\u6cd5\u5185\u90e8\u662f\u4f7f\u7528\u7684 utils.MessagePack \u7684\u5168\u5c40\u5b9e\u4f8b\uff0c\u5982\u679c\u4e0d\u80fd\u8fd4\u56de\u5e38\u89c4\u7684\u201c\u5b57\u7b26\u4e32\u201d\uff0c\u201c\u5217\u8868\u201d\uff0c\u201c\u5b57\u5178\u201d\u7684\u8fd4\u56de\u503c\uff0c\u5efa\u8bae\u914d\u7f6e\u8fd9\u51e0\u4e2a\u914d\u7f6e\u3002\n\n\u6d4b\u8bd5\u793a\u8303\n********\n\n\u5b9e\u4f8b\u5316 ZAP \u670d\u52a1\u540e\uff0c\u9700\u8981\u914d\u7f6e\u6821\u9a8c\u7b56\u7565\u3002\n\n::\n\n    zap = AsyncZAPService()\n    zap.configure_plain(\n        Settings.ZAP_DEFAULT_DOMAIN,\n        {\n            Settings.ZAP_PLAIN_DEFAULT_USER: Settings.ZAP_PLAIN_DEFAULT_PASSWORD\n        }\n    )\n    zap.start()\n\n\u7ee7\u627f Worker \u7c7b\uff0c\u7528 interface \u88c5\u9970\u5e0c\u671b\u88ab\u8fdc\u7a0b\u8c03\u7528\u7684\u65b9\u6cd5\uff0c\n\u7136\u540e\u5b9e\u4f8b\u5316\u4e00\u4e2a Server \u6765\u521b\u5efa Worker \u7684\u5b9e\u4f8b\uff0c\u8fd9\u4e2a worker \u5b9e\u4f8b\u7684\u63cf\u8ff0\u4fe1\u606f\u7531 server \u5b9e\u4f8b\u63d0\u4f9b\u3002\n\n::\n\n    from gaterpc.core import Context, Worker\n    from gaterpc.utils import interface\n\n    # Worker\n    class GRWorker(Worker):\n        @interface\n        async def atest(self, *args, **kwargs):\n            loop = self._get_loop()\n            return {\n                \"name\": \"async atest\",\n                \"args\": args,\n                \"kwargs\": kwargs,\n                \"loop_time\": loop.time()\n            }\n\n        @interface(\"process\"):\n            cpu_bound()\n\n        @interface(\"thread\")\n        def test(self, *args, **kwargs):\n            return {\n                \"name\": \"test\",\n                \"args\": args,\n                \"kwargs\": kwargs,\n                \"loop_time\": time()\n            }\n\n        @interface\n        def test_generator(self, maximum: int):\n            i = 0\n            while i < maximum:\n                yield i\n                i += 1\n\n        @interface\n        async def test_agenerator(self, maximum: int):\n            i = 0\n            while i < maximum:\n                await asyncio.sleep(0.1)\n                yield i\n                i += 1\n\n    async def test():\n        Settings.setup()\n        ctx = Context()\n        gr = Service(name=\"SRkv\")\n        gr_worker = gr.create_worker(\n            GRWorker, \"inproc://gate.worker.01\",\n            context=ctx,\n            zap_mechanism=Settings.ZAP_MECHANISM_PLAIN,\n            zap_credentials=(\n                Settings.ZAP_PLAIN_DEFAULT_USER,\n                Settings.ZAP_PLAIN_DEFAULT_PASSWORD\n            )\n        )\n        gr_worker.run()\n\n\u5f53\u8981\u6267\u884c IO \u5bc6\u96c6\u6216 CPU \u5bc6\u96c6\u578b\u64cd\u4f5c\u65f6\uff0c\u53ef\u4ee5\u901a\u8fc7 interface \u88c5\u9970\u5668\u6307\u5b9a\u662f\u5426\u4f7f\u7528\u5728\u6267\u884c\u5668\u91cc\u8fd0\u884c\uff0c\n\u4e5f\u53ef\u4ee5\u4e0d\u5728 interface \u6307\u5b9a\uff0c\u800c\u662f\u5728\u65b9\u6cd5\u5185\u4f7f\u7528 run_in_executor\uff0c\u4ee5\u4fbf\u53ef\u4ee5\u4f7f\u7528\u81ea\u5b9a\u4e49\u7684\u6267\u884c\u5668\u3002\n\n\u53e6\u5916\uff0c\u6240\u6709\u540c\u6b65\u7684\u51fd\u6570\u90fd\u4f1a\u4f7f\u7528\u9ed8\u8ba4\u6267\u884c\u5668\u6267\u884c\uff0c\u9ed8\u8ba4\u6267\u884c\u5668\u662f ThreadPoolExecutor \u5b9e\u4f8b\uff0c\u53ef\u4ee5\u4fee\u6539\u3002\n\n\u5982\u679c\u8fde\u63a5\u5730\u5740\u4f7f\u7528\u7684 inproc \u7c7b\u578b\uff0c\u4e00\u5b9a\u8981\u548c Majordomo \u4f7f\u7528\u540c\u4e00\u4e2a Context\u3002\n\n::\n\n    @interface(\"thread\")\n    async def test_io():\n        return result\n\n    @interface\n    async def test_io():\n        result = await self.run_in_executor(self.thread_executor, func, *args, **kwargs)\n        return result\n\n    @interface\n    async def test_cpu():\n        # \u5982\u679c\u9700\u8981\u548c CPU \u5bc6\u96c6\u578b\u6267\u884c\u5668\u91cc\u7684\u65b9\u6cd5\u4ea4\u6362\u6570\u636e\uff0c\n        # \u53ef\u4ee5\u4f7f\u7528 utils \u6a21\u5757\u5185\u5b9a\u4e49\u7684\u5168\u5c40\u4ee3\u7406\u7ba1\u7406\u5668 SyncManager \u6765\u521b\u5efa\u4ee3\u7406\u5bf9\u8c61\u4f7f\u7528\u3002\n        queue = SyncManager.Queue()\n        result = await self.run_in_executor(self.process_executor, func, queue, *args, **kwargs)\n        return result\n\n\u5b9e\u4f8b\u5316\u4ee3\u7406\u65f6\u8981\u7ed1\u5b9a\u4e24\u4e2a\u5730\u5740\uff0c\u4e00\u4e2a\u7528\u4e8e\u7ed9\u540e\u7aef\u670d\u52a1\u8fde\u63a5\u4e0a\u6765\uff0c\u4e00\u4e2a\u7ed9\u524d\u7aef\u5ba2\u6237\u7aef\u8fde\u63a5\u4e0a\u6765\u3002\n\n\u4e5f\u53ef\u4ee5\u53ea\u7ed1\u5b9a\u540e\u7aef\u5730\u5740\uff0c\u5c06\u4ee3\u7406\u5b9e\u4f8b\u4f5c\u4e3a\u524d\u7aef\u4f7f\u7528\uff0c\u9002\u5408\u4e0d\u957f\u671f\u81ea\u52a8\u8fd0\u884c\u7684\u4efb\u52a1\uff08\u53c2\u89c1test/testMajordomo.py\uff09\u3002\n\u8fd8\u53ef\u4ee5\u53ea\u7ed1\u5b9a\u524d\u7aef\u5730\u5740\uff0c\u5c06\u4ee3\u7406\u5b9e\u4f8b\u4f5c\u4e3a\u540e\u7aef\u4f7f\u7528\uff0c\u9002\u5408\u7b80\u5355\u7684rpc\u8c03\u7528\uff0c\u4f7f\u7528 interface \u88c5\u9970\u5668\u6765\u5b9a\u4e49\u5bf9\u5916\u63a5\u53e3\u3002\n\n::\n\n  from gaterpc.core import AMajordomo, Context\n  from gaterpc.utils import interface\n\n\n  class AM(Amajordomo):\n      # \u6240\u6709\u5185\u90e8\u5904\u7406\u7a0b\u5e8f\u90fd\u5fc5\u987b\u80fd\u63a5\u6536\u5173\u952e\u8bcd\u53c2\u6570\n      # \u4f4d\u7f6e\u53c2\u6570\u53ef\u4ee5\u81ea\u5b9a\u4e49\uff0c\u4e5f\u53ef\u4ee5\u6ca1\u6709\uff0c\u5173\u952e\u8bcd\u53c2\u6570\u4f1a\u88ab\u66f4\u65b0\u52a0\u5165\u56fa\u5b9a\u53c2\u6570\n      # kwargs.update({\n      #    \"__client_id\": client_id,\n      #    \"__request_id\": request_id,\n      # })\n      @interface\n      def internal_func(self, *args, **kwargs):\n          status_code = b\"200\" # response code\n          result = Any\n          return status_code, result\n\n  Settings.setup()\n  ctx = Context()\n  majordomo = AM(\n      context=ctx,\n      gate_zap_mechanism=Settings.ZAP_MECHANISM_PLAIN,\n      gate_zap_credentials=(\n          Settings.ZAP_PLAIN_DEFAULT_USER,\n          Settings.ZAP_PLAIN_DEFAULT_PASSWORD\n      )\n  )\n  # \u7ed1\u5b9a\u540e\u7aef\u5730\u5740\uff0c\u4e3a\u7a7a\u5219\u4f7f\u7528 Settings.WORKER_ADDR\uff0csock_opt \u53ef\u9009\u5173\u952e\u5b57\u53c2\u6570\u7528\u6765\u5b9a\u5236 socket\uff0c\u6bd4\u5982 CURVE \u914d\u7f6e\n  majordomo.bind_backend()\n  majordomo.bind_frontend(\"ipc:///tmp/gate-rpc/run/c1\")\n  # \u5982\u679c\u542f\u7528\u4e86 zap \u670d\u52a1\n  await majordomo.connect_zap(zap_addr=zipc)\n  # \u53d1\u8d77 zap \u8bf7\u6c42\u548c\u7b49\u5f85 zap \u5904\u7406\u7ed3\u679c\u662f\u4f7f\u7528\u7684 asyncio.Future \u6765\u5904\u7406\u5f02\u6b65\u7b49\u5f85\uff0c\n  # \u5e76\u4e14\u4f7f\u7528 LRUCache \u7f13\u5b58\u6bcf\u4e2a\u5730\u5740\u4f7f\u7528\u4e0d\u540c\u7684\u6821\u9a8c\u7b56\u7565\u7684\u7ed3\u679c\uff0c\u907f\u514d\u9891\u7e41\u53d1\u8d77\u9a8c\u8bc1\u8bf7\u6c42\u800c\u5bfc\u81f4\u589e\u52a0 rpc \u8c03\u7528\u7684\u65f6\u95f4\n  majordomo.run()\n\n\u5ba2\u6237\u7aef\u76f4\u63a5\u8fde\u63a5\u4ee3\u7406\u5730\u5740\uff0c\u4f7f\u7528\u70b9\u8bed\u6cd5\u8c03\u7528\u8fdc\u7a0b\u65b9\u6cd5\uff0c\u4e00\u822c\u683c\u5f0f\u662f client.\u670d\u52a1\u540d.\u65b9\u6cd5\u540d\uff0c\u5f53\u76f4\u63a5\u4f7f\u7528 client.\u65b9\u6cd5\u540d\u65f6\uff0c\u4f1a\u4f7f\u7528\u9ed8\u8ba4\u670d\u52a1\u540d\u8c03\u7528\u3002\n\n::\n\n    # Client\n    Settings.setup()\n    gr_cli = Client(\n        zap_mechanism=Settings.ZAP_MECHANISM_PLAIN,\n        zap_credentials=(\n            Settings.ZAP_PLAIN_DEFAULT_USER,\n            Settings.ZAP_PLAIN_DEFAULT_PASSWORD\n        )\n    )\n    gr_cli.connect(check_socket_addr(frontend_addr))\n    await gr_cli.GateRPC.test(\"a\", \"b\", \"c\", time=time())\n    await gr_cli.GateRPC.atest(\"a\", \"b\", \"c\", time=time())\n    async for i in await gr_cli.SRkv.test_agenerator(10):\n        print(i)\n    await gr_cli.test_huge_data()\n\n\u5ba2\u6237\u7aef\u8c03\u7528\u7684\u8fdc\u7a0b\u65b9\u6cd5\u540e\uff0c\u4f1a\u521b\u5efa\u4e00\u4e2a\u5ef6\u8fdf\u56de\u8c03\u7528\u6765\u5220\u6389\u7f13\u5b58\u7684\u5df2\u7ecf\u6267\u884c\u5b8c\u6bd5\u7684\u8bf7\u6c42\uff0c\u5305\u62ec\u8d85\u65f6\u6ca1\u62ff\u5230\u56de\u590d\u7684\u8bf7\u6c42\uff0c\n\u800c\u6d41\u5f0f\u56de\u590d\u4f1a\u6bcf\u6b21\u56de\u8c03\u65f6\u90fd\u68c0\u67e5\u4e00\u6b21\u8be5 StreamReply \u5b9e\u4f8b\u662f\u5426\u5df2\u7ecf\u7ed3\u675f\uff0c\u6ca1\u7ed3\u675f\u5c31\u518d\u521b\u5efa\u4e00\u4e2a\u5ef6\u8fdf\u56de\u8c03\u540e\u7eed\u518d\u68c0\u67e5\u3002\n\n\u66f4\u8be6\u7ec6\u7684\u6d4b\u8bd5\u7528\u4f8b\u53ef\u4ee5\u770b\u770btest\u76ee\u5f55\u4e0b\u7684\u6d4b\u8bd5\u811a\u672c\n\nGate cluster\n************\n\n\u5f53\u5e03\u7f6e\u591a\u4ee3\u7406\u96c6\u7fa4\u65f6\uff0c\u7528 bind_gate \u7ed1\u5b9a\u96c6\u7fa4\u8282\u70b9\u5730\u5740\u3002\n\nGate \u8282\u70b9\u9ed8\u8ba4\u5f00\u542f\u591a\u64ad\u7528\u4e8e\u53d1\u73b0\u5176\u4ed6\u8282\u70b9\uff0c\u591a\u64ad\u5730\u5740\u901a\u8fc7 Settings \u6216\u5355\u72ec\u7684\u4e2a\u6027\u5316\u914d\u7f6e\u6765\u914d\u7f6e\u3002\n\nGate \u96c6\u7fa4\u5185\u4e0d\u540c Gate \u8282\u70b9\u53ef\u4ee5\u627f\u8f7d\u4e0d\u540c\u7684\u4e1a\u52a1\u670d\u52a1\uff08Service\uff09\uff0c\n\u800c\u6574\u4e2a Gate \u96c6\u7fa4\u53ef\u4ee5\u63d0\u4f9b\u6240\u6709 Gate \u8282\u70b9\u627f\u8f7d\u7684\u670d\u52a1\uff0c\n\u5728 Gate \u96c6\u7fa4\u5185\u5404\u4e2a\u8282\u70b9\u4f1a\u8f6c\u53d1\u5f53\u524d\u8282\u70b9\u7684\u524d\u7aef\u8bf7\u6c42\u5230\u4efb\u4f55\u63d0\u4f9b\u8be5\u670d\u52a1\u7684\u5176\u4ed6\u8282\u70b9\u3002\n\nGate \u8282\u70b9\u53ef\u4ee5\u8bf7\u6c42\u5176\u4ed6\u8282\u70b9\u7684\u5185\u90e8\u65b9\u6cd5\uff08\u6bd4\u5982\u5206\u5e03\u5f0f\u7b97\u6cd5\u7684\u96c6\u7fa4\u8282\u70b9\u9009\u4e3e\uff09\uff0c\n\u4f8b\u5982\u53ef\u4ee5\u901a\u8fc7\u5185\u90e8\u65b9\u6cd5\uff0c\u6765\u6269\u5c55\u5206\u5e03\u5f0f\u5e94\u7528\u3002\n\nGate \u8282\u70b9\u53ef\u4ee5\u5728\u6574\u4e2a\u96c6\u7fa4\u5185\u53d1\u9001\u901a\u77e5\uff0c\u53d1\u9001\u8005\u53ea\u53d1\u4e0d\u7ba1\uff0c\u63a5\u6536\u8005\u53ea\u6536\u4e0d\u56de\uff0c\u901a\u77e5\u5904\u7406\u7a0b\u5e8f\u5728\u521d\u59cb\u5316\u540e\uff0c\n\u624b\u52a8\u4f7f\u7528 self.register_event_handler \u6765\u6ce8\u518c\u5bf9\u5e94\u901a\u77e5\u7684\u5904\u7406\u7a0b\u5e8f\uff0c\u6240\u6709\u5904\u7406\u7a0b\u5e8f\u90fd\u5fc5\u987b\u662f\u534f\u7a0b\u3002\n\nGate \u8282\u70b9\u9ed8\u8ba4\u6709\u6ce8\u518c\u4e00\u4e2a UpdateActiveService \u901a\u77e5\u4e8b\u4ef6\n\n::\n\n  class AG(Gate):\n      def __init__(\n          self,\n          port: int,\n          *,\n          identity: str = None,\n          context: Context = None,\n          heartbeat: int = None,\n          multicast: bool = True,\n          cluster_name: str = None,\n          gate_zap_mechanism: Union[str, bytes] = None,\n          gate_zap_credentials: tuple = None,\n          thread_executor: ThreadPoolExecutor = None,\n          process_executor: ProcessPoolExecutor = None\n      ):\n          super.__init__(......)\n          self.register_event_handler(\"AX\", self.ax_event_handler)\n\n  async def ax_event_handler(self, gate: RemoteGate, *body):\n      # \u901a\u77e5\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u53ea\u6709\u4e24\u4e2a\uff0c\u4e00\u4e2a\u53d1\u9001\u901a\u77e5\u7684 RemoteGate \u5b9e\u4f8b\uff0c\u4e00\u4e2a\u5305\u542b\u591a\u4e2a\u6d88\u606f\u901a\u77e5\u5185\u5bb9\n      pass\n\n\n\u7b14\u8bb0\n******\n\n\u5ba2\u6237\u7aef\u7684\u8bf7\u6c42\u548c\u56de\u590d\u7684\u5f02\u6b65\u5904\u7406\u662f\u901a\u8fc7\u521b\u5efa asyncio.Future \uff0c\u5e76\u4f7f\u7528 asyncio.wait_for \u8d85\u65f6\u7b49\u5f85\u3002\n\n::\n\n    # \u8bf7\u6c42\u8fdc\u7a0b\u65b9\u6cd5\n    request_id = await Client._request(service_name, func_name, args, kwargs)\n    response = await asyncio.wait_for(Client.replies[request_id], timeout=Client.reply_timeout)\n    # \u63a5\u6536\u56de\u590d\n    await Client.replies[request_id].set_result(body)\n\n\u5982\u679c\u81ea\u5b9a\u4e49\u65b9\u6cd5\u7684\u8fd4\u56de\u5bf9\u8c61\u7684\u5927\u5c0f\u65e0\u6cd5\u9884\u4f30\u4f1a\u6709\u591a\u5927\uff0c\u5efa\u8bae\u7528 HugeData \u5305\u88c5\u540e\u518d\u8fd4\u56de\n\n::\n\n    # data \u5fc5\u987b\u8981\u662f bytes \uff0c\u4f1a\u901a\u8fc7 SharedMemory \u6216 os.pipe \u6765\u4f20\u9012\u7ed9\u538b\u7f29\u5668\u6216\u89e3\u538b\u7f29\u5668\n    hd = HugeData(\n        Settings.HUGE_DATA_END_TAG,\n        Settings.HUGE_DATA_EXCEPT_TAG,\n        data=data, compress_module=\"gzip\", compress_level=9, blksize=1000\n    )\n    c_d = b\"\"\n    async for _d in hd.compress():\n        c_d += _d\n    # \u6216\u8005\u4e0d\u63d0\u4f9b data \uff0cHugeData \u521d\u59cb\u5316\u65f6\u4f1a\u521b\u5efa\u4e00\u4e2a os.pipe \u7684\u7ba1\u9053\uff0c\u7136\u540e\u901a\u8fc7 add_data \u8ffd\u52a0\u9700\u8981\u5904\u7406\u7684\u6570\u636e\n    hd = HugeData(\n        Settings.HUGE_DATA_END_TAG,\n        Settings.HUGE_DATA_EXCEPT_TAG,\n        compress_module=\"gzip\", compress_level=9, blksize=1000,\n        timeout=Settings.TIMEOUT\n    )\n    d = process_data()\n    # \u53ef\u4ee5\u6574\u4e2a\u76f4\u63a5\u4e22\u8fdb\u53bb\n    hd.add_data(d)\n    # \u6216\u8005\u5206\u5757\u4f20\u9012\n    for i in range(0, len(d), 1000):\n        _d = d[i: i + 1000]\n        hd.add_data(_d)\n    # \u6570\u636e\u6dfb\u52a0\u5b8c\u6bd5\u540e\uff0c\u52a1\u5fc5\u8c03\u7528\u4e00\u4e0bflush\u65b9\u6cd5\n    hd.flush()\n    d_d = b\"\"\n    # \u4f20\u9012\u672a\u5904\u7406\u6570\u636e\u548c\u63a5\u6536\u5df2\u5904\u7406\u6570\u636e\u53ef\u4ee5\u5f02\u6b65\u6267\u884c\n    async for _d in hd.decompress(1000):\n        d_d += _d\n\nHugeData \u7684 compress \u548c decompress \u65b9\u6cd5\u90fd\u4f1a\u5728\u8fdb\u7a0b\u6c60\u91cc\u6267\u884c\u589e\u91cf\u538b\u7f29\u548c\u589e\u91cf\u89e3\u538b\u7f29\uff0c\n\u8fd4\u56de\u7684\u5f02\u6b65\u751f\u6210\u5668\u6bcf\u6b21\u83b7\u53d6\u7684\u5b57\u8282\u6570\u5927\u5c0f\u53ef\u4ee5\u901a\u8fc7\u521d\u59cb\u5316 HugeData \u65f6\u4f20\u9012 blksize \u6765\u9650\u5236\uff0c\ncompress \u65b9\u6cd5\u5bf9\u6bcf\u4e00\u5757\u8fd4\u56de\u7684\u5927\u5c0f\u7684\u9650\u5236\u662f HugeData \u5185\u90e8\u5b9e\u73b0\uff0c\ndecompress \u65b9\u6cd5\u5bf9\u6bcf\u4e00\u5757\u8fd4\u56de\u7684\u5927\u5c0f\u9650\u5236\u5219\u662f\u7531\u538b\u7f29\u6a21\u5757\u6765\u5b9e\u73b0\uff0c\n\u4f1a\u5728\u8c03\u7528\u89e3\u538b\u7f29\u5668\u5b9e\u4f8b\u7684 decompress \u65b9\u6cd5\u65f6\u4f20\u9012\u4e00\u4e2a max_length \u4f4d\u7f6e\u53c2\u6570\u3002\n\n\n\u53ef\u4ee5\u7ed9 zmq.sock \u914d\u7f6e CURVE \u52a0\u5bc6\uff0c\u53ef\u4ee5\u901a\u8fc7 Settings \u8fdb\u884c\u5168\u5c40\u914d\u7f6e\uff0c\n\u4e5f\u53ef\u4ee5\u5728\u4f7f\u7528 bind \u548c connect \u65b9\u6cd5\u65f6\uff0c\u901a\u8fc7 sock_opt \u4f20\u9012\n\n::\n\n  # \u5728\u5ba2\u6237\u7aef\u548c\u670d\u52a1\u7aef\n  curve_dir = Path(__file__).parent.joinpath(\"curvekey/\")\n  if curve_dir.exists():\n      g_public, _ = zmq.auth.load_certificate(\n          curve_dir.joinpath(\"gate.key\")\n      )\n      cw_public, cw_secret = zmq.auth.load_certificate(\n          curve_dir.joinpath(\"cw.key_secret\")\n      )\n  if cw_secret:\n        Settings.ZMQ_SOCK.update(\n            {\n                z_const.CURVE_SECRETKEY: cw_secret,\n                z_const.CURVE_PUBLICKEY: cw_public,\n                z_const.CURVE_SERVERKEY: g_public,\n            }\n        )\n\n  # \u5728\u4ee3\u7406\u7aef\n  curve_dir = Path(__file__).parent.joinpath(\"curvekey/\")\n  if curve_dir.exists():\n      g_public, g_secret = zmq.auth.load_certificate(\n          curve_dir.joinpath(\"gate.key_secret\")\n      )\n      cw_public, _ = zmq.auth.load_certificate(\n          curve_dir.joinpath(\"cw.key\")\n      )\n  else:\n      g_public = g_secret = b\"\"\n      cw_public = b\"\"\n  if g_secret:\n        gr_majordomo.bind_backend(\n            sock_opt={\n                z_const.CURVE_SECRETKEY: g_secret,\n                z_const.CURVE_PUBLICKEY: g_public,\n                z_const.CURVE_SERVER: True,\n            }\n        )\n\n\n\u5728\u7528\u5230 Gate \u96c6\u7fa4\u65f6\uff0c\u4e0d\u5efa\u8bae\u4f7f\u7528 Settings.ZMQ_SOCK \u5168\u5c40\u914d\u7f6e\uff0c\n\n<del>`\u56e0\u4e3a Gate \u96c6\u7fa4\u7684\u8282\u70b9\u4e92\u8054\u662f\u4f7f\u7528\u7684\u5355\u4e00 ROUTE \u5957\u63a5\u5b57\uff0c\u7ed1\u5b9a\u548c\u8fde\u63a5\u90fd\u662f\u540c\u4e00\u4e2a ROUTE \u5957\u63a5\u5b57`</del>\n\nGate \u96c6\u7fa4\u8282\u70b9\u95f4\u4e92\u8054\uff0c\u4f1a\u5355\u72ec\u4f7f\u7528\u4e00\u4e2a DEALER \u5957\u63a5\u5b57\u8fde\u63a5\u5176\u4ed6\u8282\u70b9\u7684 ROUTE \u5957\u63a5\u5b57\n\n\u5728\u4f7f\u7528\u7531 gaterpc.utils.AQueueHandler \u505a\u4e3a\u5904\u7406\u5668\u7684\u65e5\u5fd7\u5904\u7406\u5668\u65f6\uff0c\n\u8981\u907f\u514d\u8de8\u8d8a\u7ebf\u7a0b\u548c\u8de8\u4e8b\u4ef6\u5faa\u73af\u5b9e\u4f8b\u6765\u8bb0\u5f55\u65e5\u5fd7\uff0c\n\u5728\u5c06 StreamHandler \u4f5c\u4e3a AQueueHandler \u7684 handler_class \u53c2\u6570\u65f6\u4f1a\u5c31\u9047\u5230\u8de8\u4e8b\u4ef6\u5faa\u73af\u8c03\u7528\u7684\u9519\u8bef\n\n\u8981\u6ce8\u610f\u5e76\u53d1\u592a\u591a\u65f6\uff0c\u5bf9\u5957\u63a5\u5b57\u7c7b\u578b\u7684\u9009\u62e9\u548c\u7f13\u51b2\u533a\u7684\u914d\u7f6e\uff0c\u540c\u65f6\u9002\u65f6\u7684\u8ba9\u51fa io\uff0c\n\u53ef\u4ee5\u9002\u5f53\u63d0\u9ad8 Settings \u91cc\u7684 ZMQ_SOCk \u914d\u7f6e\u91cc\u7684 z_const.HWM\uff0c\u5e76\u4e14\u63d0\u9ad8\u7cfb\u7edf\u7684\u9ed8\u8ba4\u7f13\u51b2\u533a\u5927\u5c0f\uff1b\n\n\u76ee\u524d\u5728WSL-AlmaLinux release 8.9\u4e0a\u4f7f\u7528 ipc \u534f\u8bae\u6d4b\u5f97100000\u6b21\u56de\u663e\uff0c\u5168\u90e8\u8bf7\u6c42\u53d1\u9001\u7528\u65f620\u79d2\u5de6\u53f3\uff0c\n\u5305\u542b\u6536\u5230\u6700\u540e\u4e00\u4e2a\u56de\u590d\u603b\u7528\u65f629\u79d2\u5de6\u53f3\u3002\n",
    "bugtrack_url": null,
    "license": "BSD 3-Clause License  Copyright (c) 2024, shifan All rights reserved.  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ",
    "summary": "A RPC software based on ZeroMQ with built-in Majordomo.",
    "version": "0.2.11",
    "project_urls": {
        "Homepage": "https://github.com/firejoke/gate-rpc",
        "Repository": "https://github.com/firejoke/gate-rpc"
    },
    "split_keywords": [
        "zero",
        " zeromq",
        " rpc"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0e02cd67733cdee7fd690fdb8827005036332467c241d6e0689af0ce4f12ebe6",
                "md5": "31b4de47325fc5ad20e2050d3d570d35",
                "sha256": "b0fa472a17d4d1b1a688a4817ec1a64c6038c531a7da42aa74e4f4caab0d3a81"
            },
            "downloads": -1,
            "filename": "gaterpc-0.2.11-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "31b4de47325fc5ad20e2050d3d570d35",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 51065,
            "upload_time": "2024-11-04T07:23:57",
            "upload_time_iso_8601": "2024-11-04T07:23:57.828416Z",
            "url": "https://files.pythonhosted.org/packages/0e/02/cd67733cdee7fd690fdb8827005036332467c241d6e0689af0ce4f12ebe6/gaterpc-0.2.11-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c597436873d3482c32577500d96200a1808796d8c667f477348104893d6b7d40",
                "md5": "abcff8c849604607b5ee56d9785262e6",
                "sha256": "7e118af907c728812cc9db377dd90bc073f7579a13dcb01f4111df741f89a9c5"
            },
            "downloads": -1,
            "filename": "gaterpc-0.2.11.tar.gz",
            "has_sig": false,
            "md5_digest": "abcff8c849604607b5ee56d9785262e6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 68914,
            "upload_time": "2024-11-04T07:24:00",
            "upload_time_iso_8601": "2024-11-04T07:24:00.100454Z",
            "url": "https://files.pythonhosted.org/packages/c5/97/436873d3482c32577500d96200a1808796d8c667f477348104893d6b7d40/gaterpc-0.2.11.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-04 07:24:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "firejoke",
    "github_project": "gate-rpc",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "lcname": "gaterpc"
}
        
Elapsed time: 1.18094s