0

我不知道怎么称呼它,如果有人能想到更好的标题,请告诉我,我会重命名这个问题。

这不是现实生活中的例子,但如果我能解决这种情况,我会在更大的项目中使用它。假设我必须像下面描述的那样做,我不能改变这个想法,我需要找到适合它的解决方案。目前我不允许展示原始项目的更多细节。

主意:

所以,假设我正在基于 YAPSY 插件创建类似 cron 的东西。我想将插件存储在某个目录中,有时我的守护进程会从该目录中收集所有插件,调用它们的方法并再休眠一段时间。这些插件应该能够访问一些单例,它将存储一些数据,例如插件使用的 URL 等。我还在同一进程中运行 TCP 服务器,它将修改这个单例,所以我可以在运行时自定义行为。TCP 服务器应该对单例具有读/写访问权限,并且插件应该具有只读访问权限。可能有许多单例(我的意思是,许多类表现为单例,一个类的实例不多,呃)插件可读并且可以由 TCP 服务器修改。调用插件方法时会在调用它们之前生成一些值,

问题:

如何授予对插件的只读访问权限?我想要完全隔离,所以如果singleton有字段x,它是对象,那么singleton.x(like singleton.x.y) 的字段也应该是插件的只读字段。只读意味着插件应该能够修改这些字段,但它不会对运行时的其余部分产生任何影响,因此当插件方法返回时,单例(及其字段和它们的字段等)应该与在运行插件方法之前,所以它不是真正的只读。此外,插件可能以并发方式运行,并释放 GIL 一段时间(它们可能有 IO 操作,或者只是使用 time.sleep())。

- 编辑 -

解决方案必须是多平台的,并且至少可以在 Linux、Windows 和 MacOS 上运行。

- /编辑 -

方法:

  1. 我可以尝试在单例方法中检查堆栈以查看是否有任何调用者是插件,如果是,则存储任何修改字段的原始值。然后,在插件方法调用之后,我将使用函数 restore() 将单例恢复到运行插件之前的状态。

  2. 我可以尝试在另一个进程中运行插件方法,使用多处理,将所有单例传递(很容易做到,通过使用元类来跟踪所有这些,并在新进程中重建它们,或者在某处显式存储单例)到子进程。

  3. 我可以尝试将globals()and包装locals()到一些dicts 中,这将执行与第 (1) 点类似的技巧(恢复原始值),或者将深度复制所有全局变量和局部变量,并exec使用插件方法的代码(而不是字符串,我知道这是不安全的)。

为什么上面的方法不起作用?

(1):堆栈检查通常是错误的,我想说在这种情况下是非常错误的。此外,每次调用后恢复变量可能会非常昂贵,因为插件会进行许多修改。此外,插件方法可能以并发方式运行,因此我需要在每次发布 GIL 时恢复原始值,并在获取 GIL 时恢复插件范围的值 - 这会造成很大的伤害(你能想象实现这个吗?在这一刻,我不能,我对此并不感到抱歉)。

(2): YAPSY 插件不可挑选,因此我无法将它们发送到子进程。

(3):exec()不会执行带有自由变量的代码,它看不到调用它的范围,所以我需要找到插件函数的所有自由变量(我会使用包装器,在运行时生成,像这样:

def no_arg_plugin_call():
    plugin.method(something, from_, locals_)

并通过no_args_plugin_call.__code__) 并将它们存储在 Wrapped 中locals()。此外,整个环境的深拷贝将与(1)中一样昂贵。

PS。“字段”是指“属性”,因为我(不幸的是)在 Java 等方面长大。

聚苯乙烯。如果您听说过任何类似于 YAPSY 的插件系统(它必须具有所有功能并且是轻量级的)并且会生成可挑选的实例,那对我来说就足够了;)

4

2 回答 2

0

怎么样:你 fork,然后子进程可以对任何对象做任何事情。它不会影响父母。这是和示例:

我写了 2 个插件,他们从调用者那里得到一个带有相关信息的对象,打印一条消息,然后他们修改他们得到的东西——但这不会影响原始副本。这是第一个:

import yapsy.IPlugin

class SayHi( yapsy.IPlugin.IPlugin ):
    def doSomething( self, infoForPlugins ):
        print( "plugin SayHi got: %s" % infoForPlugins[ 'SayHi' ] )
        old = infoForPlugins[ 'SayHi' ]
        infoForPlugins[ 'SayHi' ] = 'want to say "hello" instead of "%s"' % old
        print( "plugin SayHi changed info into: %s" % infoForPlugins[ 'SayHi' ] )

这是第二个,几乎相同的一个:

import yapsy.IPlugin

class SayBye( yapsy.IPlugin.IPlugin ):
    def doSomething( self, infoForPlugins ):
        print( "plugin SayBye got: %s" % infoForPlugins[ 'SayBye' ] )
        old = infoForPlugins[ 'SayBye' ]
        infoForPlugins[ 'SayBye' ] = "I don't like saying %s!!!" % old
        print( "plugin SayBye changed info into: %s" % infoForPlugins[ 'SayBye' ] )

这是 cron-thingy 代码,配有一个服务器,可让您即时修改信息(我在此示例中使用 UDP 以使其最小化,但您可以使用任何您想要的机制)

import yapsy.PluginManager
import os
import logging
import time
import threading
import socket

logging.basicConfig( level = logging.DEBUG )

class _UDPServer( threading.Thread ):
    def __init__( self, infoForPlugins ):
        threading.Thread.__init__( self )
        self._infoForPlugins = infoForPlugins
        self.daemon = True
        self._sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
        self._sock.bind( ( '', 2222 ) )

    def run( self ):
        while True:
            packet = self._sock.recv( 4096 )
            key, value = packet.split( ':' )
            self._infoForPlugins[ key ] = value

class CronThingy( object ):
    def __init__( self ):
        self._infoForPlugins = { 'SayHi': 'hi there', 'SayBye': 'bye bye' }
        self._pluginManager = yapsy.PluginManager.PluginManager()
        self._pluginManager.setPluginPlaces( [ 'plugins' ] )
        self._pluginManager.collectPlugins()
        _UDPServer( self._infoForPlugins ).start()

    def go( self ):
        while True:
            logging.info( 'info before run: %s' % self._infoForPlugins )
            self._runPlugins()
            time.sleep( 1 )
            logging.info( 'info after run: %s' % self._infoForPlugins )

    def _runPlugins( self ):
        for plugin in self._pluginManager.getAllPlugins():
            if os.fork() == 0:
                plugin.plugin_object.doSomething( self._infoForPlugins )
                quit()

if __name__ == '__main__':
    CronThingy().go()
于 2014-02-12T10:34:09.797 回答
0

与其分叉进程,不如使用代理对象“分叉”单例呢?

换句话说,当将对单例的引用传递给插件时,使用代理对象包装引用,该​​代理对象拦截所有属性获取以返回其他单例的代理并拦截所有属性集以防止实际单例的突变.

于 2014-02-17T17:10:53.933 回答