从运行时隔离功能

时间:2014-02-11 13:48:38

标签: python plugins isolation

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

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

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

问题:

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

- 编辑 -

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

- /编辑 -

的方法:

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

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

  3. 我可以尝试将globals()locals()包装到某些dict中,这样做会像点(1)那样进行类似的操作(恢复原始值)或深入复制所有全局变量和本地变量,并使用exec运行它,使用插件方法代码(不使用字符串,我知道这是不安全的)。

  4. 为什么上述方法无效?

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

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

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

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

    并传递no_args_plugin_call.__code__)并将其存储在已封装的locals()中。此外,整个环境的深层复制将与(1)中一样昂贵。

    PS。“字段”我的意思是“属性”,因为我(不幸的是)在Java上长大了。

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

2 个答案:

答案 0 :(得分:0)

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

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

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()

答案 1 :(得分:0)

不是分支过程,而是使用代理对象“分叉”单身人士呢?

换句话说,当将对单例的引用移交给插件时,使用代理对象封装引用,该​​代理对象拦截所有属性获取以返回其他单例的代理并拦截所有属性集以防止变异实际的单身人士。