diff options
author | Mark Spruiell <mes@zeroc.com> | 2017-03-07 15:25:46 -0800 |
---|---|---|
committer | Mark Spruiell <mes@zeroc.com> | 2017-03-07 15:25:46 -0800 |
commit | a6cc8dc5ed35ce0f4afcaddcdaad912bc5816556 (patch) | |
tree | 94038b3144884505db5c41006d231b5a2ae2ca3e | |
parent | Missing CSharp Glacier2/application test (diff) | |
download | ice-a6cc8dc5ed35ce0f4afcaddcdaad912bc5816556.tar.bz2 ice-a6cc8dc5ed35ce0f4afcaddcdaad912bc5816556.tar.xz ice-a6cc8dc5ed35ce0f4afcaddcdaad912bc5816556.zip |
ICE-6845 - add Python support for dispatcher
-rw-r--r-- | CHANGELOG-3.7.md | 3 | ||||
-rw-r--r-- | python/modules/IcePy/BatchRequestInterceptor.cpp | 1 | ||||
-rw-r--r-- | python/modules/IcePy/Communicator.cpp | 22 | ||||
-rw-r--r-- | python/modules/IcePy/Dispatcher.cpp | 163 | ||||
-rw-r--r-- | python/modules/IcePy/Dispatcher.h | 42 | ||||
-rw-r--r-- | python/modules/IcePy/Init.cpp | 5 | ||||
-rw-r--r-- | python/python/Ice.py | 17 | ||||
-rw-r--r-- | python/test/Ice/dispatcher/AllTests.py | 110 | ||||
-rwxr-xr-x | python/test/Ice/dispatcher/Client.py | 50 | ||||
-rwxr-xr-x | python/test/Ice/dispatcher/Dispatcher.py | 62 | ||||
-rwxr-xr-x | python/test/Ice/dispatcher/Server.py | 65 | ||||
-rw-r--r-- | python/test/Ice/dispatcher/Test.ice | 31 | ||||
-rw-r--r-- | python/test/Ice/dispatcher/TestI.py | 40 |
13 files changed, 611 insertions, 0 deletions
diff --git a/CHANGELOG-3.7.md b/CHANGELOG-3.7.md index cf4d7034590..f3ae8b1a014 100644 --- a/CHANGELOG-3.7.md +++ b/CHANGELOG-3.7.md @@ -252,8 +252,11 @@ These are the changes since Ice 3.6.3. MFruitOrange } MFruit; ``` + ## Python Changes +- Added support for the Dispatcher facility. + - The Ice communicator now implements context manager protocol. This enables the code to initialize the communicator within a `with` block. The communicator will implicitly be destroyed when the `with` block exits. diff --git a/python/modules/IcePy/BatchRequestInterceptor.cpp b/python/modules/IcePy/BatchRequestInterceptor.cpp index b4d36366ee1..95e7ed1345a 100644 --- a/python/modules/IcePy/BatchRequestInterceptor.cpp +++ b/python/modules/IcePy/BatchRequestInterceptor.cpp @@ -257,6 +257,7 @@ IcePy::BatchRequestInterceptor::enqueue(const Ice::BatchRequest& request, int qu obj->proxy = 0; PyObjectHandle tmp = PyObject_CallMethod(_interceptor.get(), STRCAST("enqueue"), STRCAST("Oii"), obj, queueCount, queueSize); + Py_DECREF(reinterpret_cast<PyObject*>(obj)); if(!tmp.get()) { throwPythonException(); diff --git a/python/modules/IcePy/Communicator.cpp b/python/modules/IcePy/Communicator.cpp index 63d19e7634d..56ffebabc9c 100644 --- a/python/modules/IcePy/Communicator.cpp +++ b/python/modules/IcePy/Communicator.cpp @@ -13,6 +13,7 @@ #include <IceUtil/DisableWarnings.h> #include <Communicator.h> #include <BatchRequestInterceptor.h> +#include <Dispatcher.h> #include <ImplicitContext.h> #include <Logger.h> #include <ObjectAdapter.h> @@ -59,6 +60,7 @@ struct CommunicatorObject IceUtil::Monitor<IceUtil::Mutex>* shutdownMonitor; WaitForShutdownThreadPtr* shutdownThread; bool shutdown; + DispatcherPtr* dispatcher; }; } @@ -80,6 +82,7 @@ communicatorNew(PyTypeObject* type, PyObject* /*args*/, PyObject* /*kwds*/) self->shutdownMonitor = new IceUtil::Monitor<IceUtil::Mutex>; self->shutdownThread = 0; self->shutdown = false; + self->dispatcher = 0; return self; } @@ -142,6 +145,7 @@ communicatorInit(CommunicatorObject* self, PyObject* args, PyObject* /*kwds*/) bool hasArgs = argList != 0; Ice::InitializationData data; + DispatcherPtr dispatcherWrapper; if(initData) { @@ -149,6 +153,7 @@ communicatorInit(CommunicatorObject* self, PyObject* args, PyObject* /*kwds*/) PyObjectHandle logger = PyObject_GetAttrString(initData, STRCAST("logger")); PyObjectHandle threadHook = PyObject_GetAttrString(initData, STRCAST("threadHook")); PyObjectHandle batchRequestInterceptor = PyObject_GetAttrString(initData, STRCAST("batchRequestInterceptor")); + PyObjectHandle dispatcher = PyObject_GetAttrString(initData, STRCAST("dispatcher")); PyErr_Clear(); // PyObject_GetAttrString sets an error on failure. @@ -172,6 +177,12 @@ communicatorInit(CommunicatorObject* self, PyObject* args, PyObject* /*kwds*/) data.threadHook = new ThreadHook(threadHook.get()); } + if(dispatcher.get() && dispatcher.get() != Py_None) + { + dispatcherWrapper = new Dispatcher(dispatcher.get()); + data.dispatcher = dispatcherWrapper; + } + if(batchRequestInterceptor.get() && batchRequestInterceptor.get() != Py_None) { data.batchRequestInterceptor = new BatchRequestInterceptor(batchRequestInterceptor.get()); @@ -269,6 +280,12 @@ communicatorInit(CommunicatorObject* self, PyObject* args, PyObject* /*kwds*/) } _communicatorMap.insert(CommunicatorMap::value_type(communicator, reinterpret_cast<PyObject*>(self))); + if(dispatcherWrapper) + { + self->dispatcher = new DispatcherPtr(dispatcherWrapper); + dispatcherWrapper->setCommunicator(communicator); + } + return 0; } @@ -323,6 +340,11 @@ communicatorDestroy(CommunicatorObject* self) vfm->destroy(); + if(self->dispatcher) + { + (*self->dispatcher)->setCommunicator(0); // Break cyclic reference. + } + // // Break cyclic reference between this object and its Python wrapper. // diff --git a/python/modules/IcePy/Dispatcher.cpp b/python/modules/IcePy/Dispatcher.cpp new file mode 100644 index 00000000000..06a6d39c991 --- /dev/null +++ b/python/modules/IcePy/Dispatcher.cpp @@ -0,0 +1,163 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#ifdef _WIN32 +# include <IceUtil/Config.h> +#endif +#include <Dispatcher.h> +#include <Connection.h> +#include <Thread.h> +#include <Ice/Initialize.h> + +using namespace std; +using namespace IcePy; + +namespace IcePy +{ + +struct DispatcherCallObject +{ + PyObject_HEAD + Ice::DispatcherCallPtr* call; +}; + +} + +#ifdef WIN32 +extern "C" +#endif +static void +dispatcherCallDealloc(DispatcherCallObject* self) +{ + delete self->call; + Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self)); +} + +#ifdef WIN32 +extern "C" +#endif +static PyObject* +dispatcherCallInvoke(DispatcherCallObject* self, PyObject* /*args*/, PyObject* /*kwds*/) +{ + AllowThreads allowThreads; // Release Python's global interpreter lock during blocking calls. + + try + { + (*self->call)->run(); + } + catch(const Ice::Exception& ex) + { + setPythonException(ex); + return 0; + } + + return incRef(Py_None); +} + +namespace IcePy +{ + +PyTypeObject DispatcherCallType = +{ + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(0, 0) + STRCAST("IcePy.DispatcherCall"), /* tp_name */ + sizeof(DispatcherCallObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + reinterpret_cast<destructor>(dispatcherCallDealloc), /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + reinterpret_cast<ternaryfunc>(dispatcherCallInvoke), /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0, /* tp_free */ + 0, /* tp_is_gc */ +}; + +} + +bool +IcePy::initDispatcher(PyObject* module) +{ + if(PyType_Ready(&DispatcherCallType) < 0) + { + return false; + } + PyTypeObject* type = &DispatcherCallType; // Necessary to prevent GCC's strict-alias warnings. + if(PyModule_AddObject(module, STRCAST("DispatcherCall"), reinterpret_cast<PyObject*>(type)) < 0) + { + return false; + } + + return true; +} + +IcePy::Dispatcher::Dispatcher(PyObject* dispatcher) : + _dispatcher(dispatcher) +{ + Py_INCREF(dispatcher); +} + +void +IcePy::Dispatcher::setCommunicator(const Ice::CommunicatorPtr& communicator) +{ + _communicator = communicator; +} + +void +IcePy::Dispatcher::dispatch(const Ice::DispatcherCallPtr& call, const Ice::ConnectionPtr& con) +{ + AdoptThread adoptThread; // Ensure the current thread is able to call into Python. + + DispatcherCallObject* obj = + reinterpret_cast<DispatcherCallObject*>(DispatcherCallType.tp_alloc(&DispatcherCallType, 0)); + if(!obj) + { + return; + } + + obj->call = new Ice::DispatcherCallPtr(call); + PyObjectHandle c = createConnection(con, _communicator); + PyObjectHandle tmp = PyObject_CallMethod(_dispatcher.get(), STRCAST("dispatch"), STRCAST("OO"), obj, c); + Py_DECREF(reinterpret_cast<PyObject*>(obj)); + if(!tmp.get()) + { + throwPythonException(); + } +} diff --git a/python/modules/IcePy/Dispatcher.h b/python/modules/IcePy/Dispatcher.h new file mode 100644 index 00000000000..156dac95bba --- /dev/null +++ b/python/modules/IcePy/Dispatcher.h @@ -0,0 +1,42 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#ifndef ICEPY_DISPATCHER_H +#define ICEPY_DISPATCHER_H + +#include <Config.h> +#include <Util.h> +#include <Ice/CommunicatorF.h> +#include <Ice/Dispatcher.h> + +namespace IcePy +{ + +bool initDispatcher(PyObject*); + +class Dispatcher : public Ice::Dispatcher +{ +public: + + Dispatcher(PyObject*); + + void setCommunicator(const Ice::CommunicatorPtr&); + + virtual void dispatch(const Ice::DispatcherCallPtr&, const Ice::ConnectionPtr&); + +private: + + PyObjectHandle _dispatcher; + Ice::CommunicatorPtr _communicator; +}; +typedef IceUtil::Handle<Dispatcher> DispatcherPtr; + +} + +#endif diff --git a/python/modules/IcePy/Init.cpp b/python/modules/IcePy/Init.cpp index 35caf6a9e15..fe1db336962 100644 --- a/python/modules/IcePy/Init.cpp +++ b/python/modules/IcePy/Init.cpp @@ -15,6 +15,7 @@ #include <Connection.h> #include <ConnectionInfo.h> #include <Current.h> +#include <Dispatcher.h> #include <Endpoint.h> #include <EndpointInfo.h> #include <ImplicitContext.h> @@ -184,6 +185,10 @@ initIcePy(void) { INIT_RETURN; } + if(!initDispatcher(module)) + { + INIT_RETURN; + } if(!initBatchRequest(module)) { INIT_RETURN; diff --git a/python/python/Ice.py b/python/python/Ice.py index b892b2938b5..f7f18d96686 100644 --- a/python/python/Ice.py +++ b/python/python/Ice.py @@ -796,6 +796,16 @@ define the enqueue method.''' implementation to confirm the batching a this request.''' pass +class Dispatcher(object): + '''Base class for a dispatcher. A subclass must define the dispatch method.''' + + def __init__(self): + pass + + def dispatch(call, con): + '''Invoked when a call needs to be dispatched. Invoke call() from the desired thread.''' + pass + class BatchRequestInterceptor(object): '''Base class for batch request interceptor. A subclass must define the enqueue method.''' @@ -820,11 +830,18 @@ properties: An instance of Ice.Properties. You can use the logger: An instance of Ice.Logger. threadHook: An object that implements ThreadNotification. + +dispatcher: An object that implements Dispatcher. + +batchRequestInterceptor: An object that implements BatchRequestInterceptor. + +valueFactoryManager: An object that implements ValueFactoryManager. ''' def __init__(self): self.properties = None self.logger = None self.threadHook = None + self.dispatcher = None self.batchRequestInterceptor = None self.valueFactoryManager = None diff --git a/python/test/Ice/dispatcher/AllTests.py b/python/test/Ice/dispatcher/AllTests.py new file mode 100644 index 00000000000..73c8df9dd4d --- /dev/null +++ b/python/test/Ice/dispatcher/AllTests.py @@ -0,0 +1,110 @@ +# ********************************************************************** +# +# Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +# +# This copy of Ice is licensed to you under the terms described in the +# ICE_LICENSE file included in this distribution. +# +# ********************************************************************** + +import Ice, Test, Dispatcher, sys, threading, random + +def test(b): + if not b: + raise RuntimeError('test assertion failed') + +class Callback: + def __init__(self): + self._called = False + self._cond = threading.Condition() + + def check(self): + with self._cond: + while not self._called: + self._cond.wait() + self._called = False + + def called(self): + with self._cond: + self._called = True + self._cond.notify() + + def response(self, f): + test(f.exception() is None) + test(Dispatcher.Dispatcher.isDispatcherThread()) + self.called() + + def exception(self, f): + test(isinstance(f.exception(), Ice.NoEndpointException)) + test(Dispatcher.Dispatcher.isDispatcherThread()) + self.called() + + def exceptionEx(self, f): + test(isinstance(f.exception(), Ice.InvocationTimeoutException)) + test(Dispatcher.Dispatcher.isDispatcherThread()) + self.called() + + def payload(self, f): + if f.exception(): + test(isinstance(f.exception(), Ice.CommunicatorDestroyedException)) + else: + test(Dispatcher.Dispatcher.isDispatcherThread()) + +def allTests(communicator, collocated): + sref = "test:default -p 12010" + obj = communicator.stringToProxy(sref) + test(obj) + + p = Test.TestIntfPrx.uncheckedCast(obj) + + sref = "testController:default -p 12011" + obj = communicator.stringToProxy(sref) + test(obj) + + testController = Test.TestIntfControllerPrx.uncheckedCast(obj) + + sys.stdout.write("testing dispatcher... ") + sys.stdout.flush() + + p.op() + + cb = Callback() + + p.opAsync().add_done_callback(cb.response) + cb.check() + + # + # Expect NoEndpointException. + # + i = p.ice_adapterId("dummy") + i.opAsync().add_done_callback(cb.exception) + cb.check() + + # + # Expect InvocationTimeoutException. + # + to = p.ice_invocationTimeout(250); + to.sleepAsync(500).add_done_callback(cb.exceptionEx) + cb.check() + + testController.holdAdapter() + + if sys.version_info[0] == 2: + b = [chr(random.randint(0, 255)) for x in range(0, 1024)] + seq = ''.join(b) + else: + b = [random.randint(0, 255) for x in range(0, 1024)] + seq = bytes(b) + + f = None + while True: + f = p.opWithPayloadAsync(seq) + f.add_done_callback(cb.payload) + if not f.is_sent_synchronously(): + break + testController.resumeAdapter() + f.result() + + print("ok") + + p.shutdown() diff --git a/python/test/Ice/dispatcher/Client.py b/python/test/Ice/dispatcher/Client.py new file mode 100755 index 00000000000..e885ab31545 --- /dev/null +++ b/python/test/Ice/dispatcher/Client.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# ********************************************************************** +# +# Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +# +# This copy of Ice is licensed to you under the terms described in the +# ICE_LICENSE file included in this distribution. +# +# ********************************************************************** + +import os, sys, traceback + +import Ice +slice_dir = Ice.getSliceDir() +if not slice_dir: + print(sys.argv[0] + ': Slice directory not found.') + sys.exit(1) + +Ice.loadSlice("'-I" + slice_dir + "' Test.ice") +import AllTests, Dispatcher + +def test(b): + if not b: + raise RuntimeError('test assertion failed') + +def run(args, communicator): + AllTests.allTests(communicator, False) + return True + +try: + initData = Ice.InitializationData() + initData.properties = Ice.createProperties(sys.argv) + + # + # Limit the send buffer size, this test relies on the socket + # send() blocking after sending a given amount of data. + # + initData.properties.setProperty("Ice.TCP.SndSize", "50000"); + + initData.dispatcher = Dispatcher.Dispatcher() + + with Ice.initialize(sys.argv, initData) as communicator: + status = run(sys.argv, communicator) +except: + traceback.print_exc() + status = False + +Dispatcher.Dispatcher.terminate() + +sys.exit(not status) diff --git a/python/test/Ice/dispatcher/Dispatcher.py b/python/test/Ice/dispatcher/Dispatcher.py new file mode 100755 index 00000000000..1f4fa4ee1e4 --- /dev/null +++ b/python/test/Ice/dispatcher/Dispatcher.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# ********************************************************************** +# +# Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +# +# This copy of Ice is licensed to you under the terms described in the +# ICE_LICENSE file included in this distribution. +# +# ********************************************************************** + +import Ice, os, sys, traceback, time, threading + +def test(b): + if not b: + raise RuntimeError('test assertion failed') + +class Dispatcher(Ice.Dispatcher): + def __init__(self): + self._calls = [] + self._terminated = False + self._cond = threading.Condition() + self._thread = threading.Thread(target=self.run) + self._thread.start() + Dispatcher._instance = self + + def dispatch(self, call, con): + with self._cond: + self._calls.append(call) + if len(self._calls) == 1: + self._cond.notify() + + def run(self): + while True: + call = None + with self._cond: + while not self._terminated and len(self._calls) == 0: + self._cond.wait() + if len(self._calls) > 0: + call = self._calls.pop(0) + elif self._terminated: + # Terminate only once all calls are dispatched. + return + + if call: + try: + call() + except: + # Exceptions should never propagate here. + test(False) + + @staticmethod + def terminate(): + with Dispatcher._instance._cond: + Dispatcher._instance._terminated = True + Dispatcher._instance._cond.notify() + + Dispatcher._instance._thread.join() + Dispatcher._instance = None + + @staticmethod + def isDispatcherThread(): + return threading.current_thread() == Dispatcher._instance._thread diff --git a/python/test/Ice/dispatcher/Server.py b/python/test/Ice/dispatcher/Server.py new file mode 100755 index 00000000000..59c9438eaa6 --- /dev/null +++ b/python/test/Ice/dispatcher/Server.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# ********************************************************************** +# +# Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +# +# This copy of Ice is licensed to you under the terms described in the +# ICE_LICENSE file included in this distribution. +# +# ********************************************************************** + +import os, sys, traceback +import Ice +slice_dir = Ice.getSliceDir() +if not slice_dir: + print(sys.argv[0] + ': Slice directory not found.') + sys.exit(1) + +Ice.loadSlice('"-I' + slice_dir + '" Test.ice') +import Test, TestI, Dispatcher + +def run(args, communicator): + communicator.getProperties().setProperty("TestAdapter.Endpoints", "default -p 12010") + communicator.getProperties().setProperty("ControllerAdapter.Endpoints", "default -p 12011") + communicator.getProperties().setProperty("ControllerAdapter.ThreadPool.Size", "1") + + adapter = communicator.createObjectAdapter("TestAdapter") + adapter2 = communicator.createObjectAdapter("ControllerAdapter") + + testController = TestI.TestIntfControllerI(adapter) + + adapter.add(TestI.TestIntfI(), Ice.stringToIdentity("test")) + adapter.activate() + + adapter2.add(testController, Ice.stringToIdentity("testController")) + adapter2.activate() + + communicator.waitForShutdown() + return True + +try: + initData = Ice.InitializationData() + initData.properties = Ice.createProperties(sys.argv) + + # + # This test kills connections, so we don't want warnings. + # + initData.properties.setProperty("Ice.Warn.Connections", "0") + + # + # Limit the recv buffer size, this test relies on the socket + # send() blocking after sending a given amount of data. + # + initData.properties.setProperty("Ice.TCP.RcvSize", "50000") + + initData.dispatcher = Dispatcher.Dispatcher() + + with Ice.initialize(sys.argv, initData) as communicator: + status = run(sys.argv, communicator) +except: + traceback.print_exc() + status = False + +Dispatcher.Dispatcher.terminate() + +sys.exit(not status) diff --git a/python/test/Ice/dispatcher/Test.ice b/python/test/Ice/dispatcher/Test.ice new file mode 100644 index 00000000000..53e7d6ee031 --- /dev/null +++ b/python/test/Ice/dispatcher/Test.ice @@ -0,0 +1,31 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#pragma once + +#include <Ice/BuiltinSequences.ice> + +module Test +{ + +interface TestIntf +{ + void op(); + void sleep(int to); + void opWithPayload(Ice::ByteSeq seq); + void shutdown(); +}; + +interface TestIntfController +{ + void holdAdapter(); + void resumeAdapter(); +}; + +}; diff --git a/python/test/Ice/dispatcher/TestI.py b/python/test/Ice/dispatcher/TestI.py new file mode 100644 index 00000000000..2dfa9cc3f52 --- /dev/null +++ b/python/test/Ice/dispatcher/TestI.py @@ -0,0 +1,40 @@ +# ********************************************************************** +# +# Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved. +# +# This copy of Ice is licensed to you under the terms described in the +# ICE_LICENSE file included in this distribution. +# +# ********************************************************************** + +import Ice, Test, Dispatcher, time + +def test(b): + if not b: + raise RuntimeError('test assertion failed') + +class TestIntfI(Test._TestIntfDisp): + def op(self, current=None): + test(Dispatcher.Dispatcher.isDispatcherThread()) + + def sleep(self, ms, current=None): + time.sleep(ms / 1000.0) + + def opWithPayload(self, bytes, current=None): + test(Dispatcher.Dispatcher.isDispatcherThread()) + + def shutdown(self, current=None): + test(Dispatcher.Dispatcher.isDispatcherThread()) + current.adapter.getCommunicator().shutdown() + +class TestIntfControllerI(Test._TestIntfControllerDisp): + def __init__(self, adapter): + self._adapter = adapter + + def holdAdapter(self, current=None): + test(Dispatcher.Dispatcher.isDispatcherThread()) + self._adapter.hold() + + def resumeAdapter(self, current=None): + test(Dispatcher.Dispatcher.isDispatcherThread()) + self._adapter.activate() |