[C++-sig] Re: boost::python and threads
David Abrahams
dave at boost-consulting.com
Mon Jul 7 22:13:39 CEST 2003
Vladimir Vukicevic <vladimir at pobox.com> writes:
> David Abrahams wrote:
>
> >>FooProxy is a remote object that I get as a result of calling various
> >>functions to get a proxy -- it has a "void bar();" (non-virtual) that
> >>I can call. >
> >
> >Hmm. This is totally off-topic, but it seems odd to me (and
> >inconvenient, in C++) that FooProxy is not derived from Foo.
> >
> Live servants and proxies live in different class hierarchies; the
> Proxy objects just know how to add their tag to a packet and serialize
> arguments to operations, whereas Foo might have additional data
> members and the like.
Which is why Foo ought to be an empty abstract base class. But anyway...
> >>It's this problem that I'm directly dealing with -- I can't figure
> >>out a way to wrap class FooProxy such that I can manage the current
> >>ThreadState and the GIL around them. I can create another wrapper
> >>class
> >
> >Do you mean like a virtual-function-dispatching class, the kind with
> >the initial "PyObject* self" argument to its constructors?
>
> Yes.
>
> >>that does the right thing and calls FooProxy::bar(), but
> >>functions are returning a FooProxy (not mywrapper_for_FooProxy)
> >
> >
> >I thought you said that all the C++ interfaces passed FooProxyHandle
> >(?)
>
> Sorry, my mistake.. it is a FooProxyHandle. Internally FooProxy is
> typedef'd to ::Ice::ProxyHandle< ::IceProxy::Foo> :)
See boost/python/converter/register_ptr_to_python.hpp in the CVS.
> >>and I already have a Handle<FooProxy> as a wrapper specified in the
> >>boost::python class_... so I end up with incorrect types half the
> >>time.
> >
> >
> >Specifically, what problem are you having? Could you show a function
> >you are unable to wrap properly and describe the specific problems
> >you're having?
>
> Sure; see end of message, as it's somewhat lengthy and I didn't want
> to clutter up the replies. (*)
>
> >>However, an alternative solution that I was thinking of is to bake the
> >>thread smarts directly into boost::python. >
> >
> >I have always thought that should happen in some form or other anyway.
> >
> >>It seems to me that call_method and similar should always be wrapped
> >>with a PyGILState_Ensure/PyGILState_Release. >
> >
> >Some people know their extensions are only being called on the main
> >thread and not everyone will be ready to pay the cost for acquiring
> >the GIL on every callback. IMO supplying a GILState class which
> >acquires on construction and releases on destruction *should* be
> >enough.
>
> Hmm, I'm not sure what you mean; where would you apply the GILState
> class? I agree though, acquiring the GIL on every callback is
> heavyhanded, but necessary for some applications (like mine, where I
> don't know exactly which thread a callback will get invoked on for a
> particular object instance).
I guess from your other messages you figured that out.
> >>Also, an additional call policy could be added, something like
> >>"allow_threads", which would cause b::p to call
> >>Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS around the function
> >>invocation on C++. This seems like a much more generally useful
> >>solution than me trying to do the dance in wrapper classes.
> >
> >I think some change to the requirements and usage of CallPolicies
> >would be neccessary in order to support that (see my reply to
> >Nikolay), but I agree with the general idea.
> >
> >>Is this the right way to go? >
> >Probably. I also think that some people want automatic
> >Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS for *all* of their wrapped
> >functions, and that should probably be configurable, too. You have to
> >be careful with that, though: once you do Py_BEGIN_ALLOW_THREADS you
> >can't use any Boost.Python facilities which use the Python 'C' API
> >(e.g. object, handle<>, ...) until you reach Py_END_ALLOW_THREADS.
>
> Yep; I assume invoke.hpp is the lowest-level at which the C++
> function is actually invoked.
Close.
> >>If so, I'd appreciate any pointers on implementing a new call
> >>policy, especially if it's possible to both specify allow_threads
> >>and a return_value_policy.
> >
> >I think you'd better start by looking at the macro definition of
> >Py_BEGIN_ALLOW_THREADS et al and then at how policies are used to see
> >how state can be stored for the duration of the call they affect, as
> >the macros are wont to do.
>
> * Specific function problem:
>
> Each ProxyHandle<Proxy::Foo> exports (via the ProxyHandle class) a
> checkedCast() static member, that can be used to convert
> ProxyHandle<Proxy::Object> (which is the root of all proxies) to a
> specific proxy type. checkedCast() checkedcast actually creates a new
> ProxyHandle<Proxy::Foo> object, and then copies the target server
> information into it from the ProxyHandle<Proxy::Object>, so there's no
> "cast" in the C++ sense going on (though it does check if it can
> dynamic_cast<> the Proxy::Object* to a Proxy::Foo*, and if so, just
> does that).
>
> To have support for checkedCast in python, I create wrapper functions
> such as:
>
> namespace pyce_Hello_casts {
> ::IceInternal::ProxyHandle< ::IceProxy::Hello> _Hello_checkedCast
^^^^^^^^^^^^^^^^^^
This name is reserved for your C++ implementation.
> (const ::Ice::ObjectPrx& o)
> {
> return ::IceInternal::ProxyHandle< ::IceProxy::Hello>::checkedCast (o);
> }
> };
>
> and my class_<> def looks like:
>
> class_< ::IceProxy::Hello ,
> ::IceInternal::ProxyHandle< ::IceProxy::Hello >,
> bases< ::IceProxy::Ice::Object >,
> boost::noncopyable >
> ("HelloPrx")
> .def("sayHello", &::IceProxy::Hello::sayHello)
> .def("checkedCast", pyce_Hello_casts::_Hello_checkedCast)
> .staticmethod("checkedCast")
> ;
>
> This all works fine, until I want to intercept the call to sayHello()
> (to save/restore the ThreadState goop). I can create a wrapper that
> derives from ::IceProxy::Hello, but then I have to deal with that
> PyObject* constructor, for which I have no need for here -- I'm never
> going to utilize call_method, and HelloPrx will never be derived from
> in python-land. The sayHello() and other member functions are also
> not virtual. I could do a dance with a custom-written overload
> dispatch for each function. If I went the wrapper route, I would need
> to implement my own version of checkedCast that can convert to my
> wrapper, and add some implicitly_convertible statements, and make sure
> that no references to ::IceProxy::Hello (or the equivalent handle) get
> exposed anywhere.. certainly feasable, but a pain, especially for
> cases where I get a handle to a ::IceProxy::Hello in C++ and I'd have
> to convert.
>
> But both approaches seem like they're far too verbose. I'll probably
> create a call_method_with_threads<> such that the non-GIL-acquiring
> call_method<> is still available; extending call policies would allow
> a nice implementation for GIL-releasing and non-GIL-releasing C++
> calls in caller.hpp, but the problem with the ResultConverter being
> called from invoke.hpp still remains. If there was a base class for
> all result converters, it could perhaps acquire the GIL in its
> constructor and release in the destructor, though no such class exists
> (that I can see?), and it would be yet another mutex
> acquisition/release.
This is all very complicated sounding, and I can't tell whether it's
still an issue in light of the guard object idea, and I have so much
to catch up on I don't think I have time to analyze. If there's
still an issue here, could you boil it down a bit?
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
More information about the Cplusplus-sig
mailing list