PageRenderTime 65ms CodeModel.GetById 18ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/llcommon/lleventcoro.h

https://bitbucket.org/lindenlab/viewer-beta/
C++ Header | 569 lines | 261 code | 36 blank | 272 comment | 2 complexity | 946617ee109192d7cb52ff363444de38 MD5 | raw file
  1/**
  2 * @file   lleventcoro.h
  3 * @author Nat Goodspeed
  4 * @date   2009-04-29
  5 * @brief  Utilities to interface between coroutines and events.
  6 * 
  7 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
  8 * Second Life Viewer Source Code
  9 * Copyright (C) 2010, Linden Research, Inc.
 10 * 
 11 * This library is free software; you can redistribute it and/or
 12 * modify it under the terms of the GNU Lesser General Public
 13 * License as published by the Free Software Foundation;
 14 * version 2.1 of the License only.
 15 * 
 16 * This library is distributed in the hope that it will be useful,
 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19 * Lesser General Public License for more details.
 20 * 
 21 * You should have received a copy of the GNU Lesser General Public
 22 * License along with this library; if not, write to the Free Software
 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 24 * 
 25 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 26 * $/LicenseInfo$
 27 */
 28
 29#if ! defined(LL_LLEVENTCORO_H)
 30#define LL_LLEVENTCORO_H
 31
 32#include <boost/coroutine/coroutine.hpp>
 33#include <boost/coroutine/future.hpp>
 34#include <boost/optional.hpp>
 35#include <string>
 36#include <stdexcept>
 37#include "llevents.h"
 38#include "llerror.h"
 39
 40/**
 41 * Like LLListenerOrPumpName, this is a class intended for parameter lists:
 42 * accept a <tt>const LLEventPumpOrPumpName&</tt> and you can accept either an
 43 * <tt>LLEventPump&</tt> or its string name. For a single parameter that could
 44 * be either, it's not hard to overload the function -- but as soon as you
 45 * want to accept two such parameters, this is cheaper than four overloads.
 46 */
 47class LLEventPumpOrPumpName
 48{
 49public:
 50    /// Pass an actual LLEventPump&
 51    LLEventPumpOrPumpName(LLEventPump& pump):
 52        mPump(pump)
 53    {}
 54    /// Pass the string name of an LLEventPump
 55    LLEventPumpOrPumpName(const std::string& pumpname):
 56        mPump(LLEventPumps::instance().obtain(pumpname))
 57    {}
 58    /// Pass string constant name of an LLEventPump. This override must be
 59    /// explicit, since otherwise passing <tt>const char*</tt> to a function
 60    /// accepting <tt>const LLEventPumpOrPumpName&</tt> would require two
 61    /// different implicit conversions: <tt>const char*</tt> -> <tt>const
 62    /// std::string&</tt> -> <tt>const LLEventPumpOrPumpName&</tt>.
 63    LLEventPumpOrPumpName(const char* pumpname):
 64        mPump(LLEventPumps::instance().obtain(pumpname))
 65    {}
 66    /// Unspecified: "I choose not to identify an LLEventPump."
 67    LLEventPumpOrPumpName() {}
 68    operator LLEventPump& () const { return *mPump; }
 69    LLEventPump& getPump() const { return *mPump; }
 70    operator bool() const { return mPump; }
 71    bool operator!() const { return ! mPump; }
 72
 73private:
 74    boost::optional<LLEventPump&> mPump;
 75};
 76
 77/// This is an adapter for a signature like void LISTENER(const LLSD&), which
 78/// isn't a valid LLEventPump listener: such listeners should return bool.
 79template <typename LISTENER>
 80class LLVoidListener
 81{
 82public:
 83    LLVoidListener(const LISTENER& listener):
 84        mListener(listener)
 85    {}
 86    bool operator()(const LLSD& event)
 87    {
 88        mListener(event);
 89        // don't swallow the event, let other listeners see it
 90        return false;
 91    }
 92private:
 93    LISTENER mListener;
 94};
 95
 96/// LLVoidListener helper function to infer the type of the LISTENER
 97template <typename LISTENER>
 98LLVoidListener<LISTENER> voidlistener(const LISTENER& listener)
 99{
100    return LLVoidListener<LISTENER>(listener);
101}
102
103namespace LLEventDetail
104{
105    /**
106     * waitForEventOn() permits a coroutine to temporarily listen on an
107     * LLEventPump any number of times. We don't really want to have to ask
108     * the caller to label each such call with a distinct string; the whole
109     * point of waitForEventOn() is to present a nice sequential interface to
110     * the underlying LLEventPump-with-named-listeners machinery. So we'll use
111     * LLEventPump::inventName() to generate a distinct name for each
112     * temporary listener. On the other hand, because a given coroutine might
113     * call waitForEventOn() any number of times, we don't really want to
114     * consume an arbitrary number of generated inventName()s: that namespace,
115     * though large, is nonetheless finite. So we memoize an invented name for
116     * each distinct coroutine instance (each different 'self' object). We
117     * can't know the type of 'self', because it depends on the coroutine
118     * body's signature. So we cast its address to void*, looking for distinct
119     * pointer values. Yes, that means that an early coroutine could cache a
120     * value here, then be destroyed, only to be supplanted by a later
121     * coroutine (of the same or different type), and we'll end up
122     * "recognizing" the second one and reusing the listener name -- but
123     * that's okay, since it won't collide with any listener name used by the
124     * earlier coroutine since that earlier coroutine no longer exists.
125     */
126    template <typename COROUTINE_SELF>
127    std::string listenerNameForCoro(COROUTINE_SELF& self)
128    {
129        return listenerNameForCoroImpl(self.get_id());
130    }
131
132    /// Implementation for listenerNameForCoro()
133    LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id);
134
135    /**
136     * Implement behavior described for postAndWait()'s @a replyPumpNamePath
137     * parameter:
138     *
139     * * If <tt>path.isUndefined()</tt>, do nothing.
140     * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value
141     *   into <tt>dest[path.asString()]</tt>.
142     * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a
143     *   value into <tt>dest[path.asInteger()]</tt>.
144     * * If <tt>path.isArray()</tt>, iteratively apply the rules above to step
145     *   down through the structure of @a dest. The last array entry in @a
146     *   path specifies the entry in the lowest-level structure in @a dest
147     *   into which to store @a value.
148     *
149     * @note
150     * In the degenerate case in which @a path is an empty array, @a dest will
151     * @em become @a value rather than @em containing it.
152     */
153    LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value);
154} // namespace LLEventDetail
155
156/**
157 * Post specified LLSD event on the specified LLEventPump, then wait for a
158 * response on specified other LLEventPump. This is more than mere
159 * convenience: the difference between this function and the sequence
160 * @code
161 * requestPump.post(myEvent);
162 * LLSD reply = waitForEventOn(self, replyPump);
163 * @endcode
164 * is that the sequence above fails if the reply is posted immediately on
165 * @a replyPump, that is, before <tt>requestPump.post()</tt> returns. In the
166 * sequence above, the running coroutine isn't even listening on @a replyPump
167 * until <tt>requestPump.post()</tt> returns and @c waitForEventOn() is
168 * entered. Therefore, the coroutine completely misses an immediate reply
169 * event, making it wait indefinitely.
170 *
171 * By contrast, postAndWait() listens on the @a replyPump @em before posting
172 * the specified LLSD event on the specified @a requestPump.
173 *
174 * @param self The @c self object passed into a coroutine
175 * @param event LLSD data to be posted on @a requestPump
176 * @param requestPump an LLEventPump on which to post @a event. Pass either
177 * the LLEventPump& or its string name. However, if you pass a
178 * default-constructed @c LLEventPumpOrPumpName, we skip the post() call.
179 * @param replyPump an LLEventPump on which postAndWait() will listen for a
180 * reply. Pass either the LLEventPump& or its string name. The calling
181 * coroutine will wait until that reply arrives. (If you're concerned about a
182 * reply that might not arrive, please see also LLEventTimeout.)
183 * @param replyPumpNamePath specifies the location within @a event in which to
184 * store <tt>replyPump.getName()</tt>. This is a strictly optional convenience
185 * feature; obviously you can store the name in @a event "by hand" if desired.
186 * @a replyPumpNamePath can be specified in any of four forms:
187 * * @c isUndefined() (default-constructed LLSD object): do nothing. This is
188 *   the default behavior if you omit @a replyPumpNamePath.
189 * * @c isInteger(): @a event is an array. Store <tt>replyPump.getName()</tt>
190 *   in <tt>event[replyPumpNamePath.asInteger()]</tt>.
191 * * @c isString(): @a event is a map. Store <tt>replyPump.getName()</tt> in
192 *   <tt>event[replyPumpNamePath.asString()]</tt>.
193 * * @c isArray(): @a event has several levels of structure, e.g. map of
194 *   maps, array of arrays, array of maps, map of arrays, ... Store
195 *   <tt>replyPump.getName()</tt> in
196 *   <tt>event[replyPumpNamePath[0]][replyPumpNamePath[1]]...</tt> In other
197 *   words, examine each array entry in @a replyPumpNamePath in turn. If it's an
198 *   <tt>LLSD::String</tt>, the current level of @a event is a map; step down to
199 *   that map entry. If it's an <tt>LLSD::Integer</tt>, the current level of @a
200 *   event is an array; step down to that array entry. The last array entry in
201 *   @a replyPumpNamePath specifies the entry in the lowest-level structure in
202 *   @a event into which to store <tt>replyPump.getName()</tt>.
203 */
204template <typename SELF>
205LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump,
206                 const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD())
207{
208    // declare the future
209    boost::coroutines::future<LLSD> future(self);
210    // make a callback that will assign a value to the future, and listen on
211    // the specified LLEventPump with that callback
212    std::string listenerName(LLEventDetail::listenerNameForCoro(self));
213    LLTempBoundListener connection(
214        replyPump.getPump().listen(listenerName,
215                                   voidlistener(boost::coroutines::make_callback(future))));
216    // skip the "post" part if requestPump is default-constructed
217    if (requestPump)
218    {
219        // If replyPumpNamePath is non-empty, store the replyPump name in the
220        // request event.
221        LLSD modevent(event);
222        LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName());
223		LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName
224                                 << " posting to " << requestPump.getPump().getName()
225								 << LL_ENDL;
226
227		// *NOTE:Mani - Removed because modevent could contain user's hashed passwd.
228		//                         << ": " << modevent << LL_ENDL;
229        requestPump.getPump().post(modevent);
230    }
231    LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName
232                             << " about to wait on LLEventPump " << replyPump.getPump().getName()
233                             << LL_ENDL;
234    // trying to dereference ("resolve") the future makes us wait for it
235    LLSD value(*future);
236    LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName
237                             << " resuming with " << value << LL_ENDL;
238    // returning should disconnect the connection
239    return value;
240}
241
242/// Wait for the next event on the specified LLEventPump. Pass either the
243/// LLEventPump& or its string name.
244template <typename SELF>
245LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump)
246{
247    // This is now a convenience wrapper for postAndWait().
248    return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump);
249}
250
251/// return type for two-pump variant of waitForEventOn()
252typedef std::pair<LLSD, int> LLEventWithID;
253
254namespace LLEventDetail
255{
256    /**
257     * This helper is specifically for the two-pump version of waitForEventOn().
258     * We use a single future object, but we want to listen on two pumps with it.
259     * Since we must still adapt from (the callable constructed by)
260     * boost::coroutines::make_callback() (void return) to provide an event
261     * listener (bool return), we've adapted LLVoidListener for the purpose. The
262     * basic idea is that we construct a distinct instance of WaitForEventOnHelper
263     * -- binding different instance data -- for each of the pumps. Then, when a
264     * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine
265     * that LLSD with its discriminator to feed the future object.
266     */
267    template <typename LISTENER>
268    class WaitForEventOnHelper
269    {
270    public:
271        WaitForEventOnHelper(const LISTENER& listener, int discriminator):
272            mListener(listener),
273            mDiscrim(discriminator)
274        {}
275        // this signature is required for an LLEventPump listener
276        bool operator()(const LLSD& event)
277        {
278            // our future object is defined to accept LLEventWithID
279            mListener(LLEventWithID(event, mDiscrim));
280            // don't swallow the event, let other listeners see it
281            return false;
282        }
283    private:
284        LISTENER mListener;
285        const int mDiscrim;
286    };
287
288    /// WaitForEventOnHelper type-inference helper
289    template <typename LISTENER>
290    WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator)
291    {
292        return WaitForEventOnHelper<LISTENER>(listener, discriminator);
293    }
294} // namespace LLEventDetail
295
296/**
297 * This function waits for a reply on either of two specified LLEventPumps.
298 * Otherwise, it closely resembles postAndWait(); please see the documentation
299 * for that function for detailed parameter info.
300 *
301 * While we could have implemented the single-pump variant in terms of this
302 * one, there's enough added complexity here to make it worthwhile to give the
303 * single-pump variant its own straightforward implementation. Conversely,
304 * though we could use preprocessor logic to generate n-pump overloads up to
305 * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump
306 * overload exists because certain event APIs are defined in terms of a reply
307 * LLEventPump and an error LLEventPump.
308 *
309 * The LLEventWithID return value provides not only the received event, but
310 * the index of the pump on which it arrived (0 or 1).
311 *
312 * @note
313 * I'd have preferred to overload the name postAndWait() for both signatures.
314 * But consider the following ambiguous call:
315 * @code
316 * postAndWait(self, LLSD(), requestPump, replyPump, "someString");
317 * @endcode
318 * "someString" could be converted to either LLSD (@a replyPumpNamePath for
319 * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump
320 * function).
321 *
322 * It seems less burdensome to write postAndWait2() than to write either
323 * LLSD("someString") or LLEventOrPumpName("someString").
324 */
325template <typename SELF>
326LLEventWithID postAndWait2(SELF& self, const LLSD& event,
327                           const LLEventPumpOrPumpName& requestPump,
328                           const LLEventPumpOrPumpName& replyPump0,
329                           const LLEventPumpOrPumpName& replyPump1,
330                           const LLSD& replyPump0NamePath=LLSD(),
331                           const LLSD& replyPump1NamePath=LLSD())
332{
333    // declare the future
334    boost::coroutines::future<LLEventWithID> future(self);
335    // either callback will assign a value to this future; listen on
336    // each specified LLEventPump with a callback
337    std::string name(LLEventDetail::listenerNameForCoro(self));
338    LLTempBoundListener connection0(
339        replyPump0.getPump().listen(name + "a",
340                               LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 0)));
341    LLTempBoundListener connection1(
342        replyPump1.getPump().listen(name + "b",
343                               LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 1)));
344    // skip the "post" part if requestPump is default-constructed
345    if (requestPump)
346    {
347        // If either replyPumpNamePath is non-empty, store the corresponding
348        // replyPump name in the request event.
349        LLSD modevent(event);
350        LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath,
351                                       replyPump0.getPump().getName());
352        LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath,
353                                       replyPump1.getPump().getName());
354        LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name
355                                 << " posting to " << requestPump.getPump().getName()
356                                 << ": " << modevent << LL_ENDL;
357        requestPump.getPump().post(modevent);
358    }
359    LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name
360                             << " about to wait on LLEventPumps " << replyPump0.getPump().getName()
361                             << ", " << replyPump1.getPump().getName() << LL_ENDL;
362    // trying to dereference ("resolve") the future makes us wait for it
363    LLEventWithID value(*future);
364    LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name
365                             << " resuming with (" << value.first << ", " << value.second << ")"
366                             << LL_ENDL;
367    // returning should disconnect both connections
368    return value;
369}
370
371/**
372 * Wait for the next event on either of two specified LLEventPumps.
373 */
374template <typename SELF>
375LLEventWithID
376waitForEventOn(SELF& self,
377               const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
378{
379    // This is now a convenience wrapper for postAndWait2().
380    return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
381}
382
383/**
384 * Helper for the two-pump variant of waitForEventOn(), e.g.:
385 *
386 * @code
387 * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump),
388 *                             "error response from login.cgi");
389 * @endcode
390 *
391 * Examines an LLEventWithID, assuming that the second pump (pump 1) is
392 * listening for an error indication. If the incoming data arrived on pump 1,
393 * throw an LLErrorEvent exception. If the incoming data arrived on pump 0,
394 * just return it. Since a normal return can only be from pump 0, we no longer
395 * need the LLEventWithID's discriminator int; we can just return the LLSD.
396 *
397 * @note I'm not worried about introducing the (fairly generic) name
398 * errorException() into global namespace, because how many other overloads of
399 * the same name are going to accept an LLEventWithID parameter?
400 */
401LLSD errorException(const LLEventWithID& result, const std::string& desc);
402
403/**
404 * Exception thrown by errorException(). We don't call this LLEventError
405 * because it's not an error in event processing: rather, this exception
406 * announces an event that bears error information (for some other API).
407 */
408class LL_COMMON_API LLErrorEvent: public std::runtime_error
409{
410public:
411    LLErrorEvent(const std::string& what, const LLSD& data):
412        std::runtime_error(what),
413        mData(data)
414    {}
415    virtual ~LLErrorEvent() throw() {}
416
417    LLSD getData() const { return mData; }
418
419private:
420    LLSD mData;
421};
422
423/**
424 * Like errorException(), save that this trips a fatal error using LL_ERRS
425 * rather than throwing an exception.
426 */
427LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc);
428
429/**
430 * Certain event APIs require the name of an LLEventPump on which they should
431 * post results. While it works to invent a distinct name and let
432 * LLEventPumps::obtain() instantiate the LLEventPump as a "named singleton,"
433 * in a certain sense it's more robust to instantiate a local LLEventPump and
434 * provide its name instead. This class packages the following idiom:
435 *
436 * 1. Instantiate a local LLCoroEventPump, with an optional name prefix.
437 * 2. Provide its actual name to the event API in question as the name of the
438 *    reply LLEventPump.
439 * 3. Initiate the request to the event API.
440 * 4. Call your LLEventTempStream's wait() method to wait for the reply.
441 * 5. Let the LLCoroEventPump go out of scope.
442 */
443class LL_COMMON_API LLCoroEventPump
444{
445public:
446    LLCoroEventPump(const std::string& name="coro"):
447        mPump(name, true)           // allow tweaking the pump instance name
448    {}
449    /// It's typical to request the LLEventPump name to direct an event API to
450    /// send its response to this pump.
451    std::string getName() const { return mPump.getName(); }
452    /// Less typically, we'd request the pump itself for some reason.
453    LLEventPump& getPump() { return mPump; }
454
455    /**
456     * Wait for an event on this LLEventPump.
457     *
458     * @note
459     * The other major usage pattern we considered was to bind @c self at
460     * LLCoroEventPump construction time, which would avoid passing the
461     * parameter to each wait() call. But if we were going to bind @c self as
462     * a class member, we'd need to specify a class template parameter
463     * indicating its type. The big advantage of passing it to the wait() call
464     * is that the type can be implicit.
465     */
466    template <typename SELF>
467    LLSD wait(SELF& self)
468    {
469        return waitForEventOn(self, mPump);
470    }
471
472    template <typename SELF>
473    LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump,
474                     const LLSD& replyPumpNamePath=LLSD())
475    {
476        return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath);
477    }
478
479private:
480    LLEventStream mPump;
481};
482
483/**
484 * Other event APIs require the names of two different LLEventPumps: one for
485 * success response, the other for error response. Extend LLCoroEventPump
486 * for the two-pump use case.
487 */
488class LL_COMMON_API LLCoroEventPumps
489{
490public:
491    LLCoroEventPumps(const std::string& name="coro",
492                     const std::string& suff0="Reply",
493                     const std::string& suff1="Error"):
494        mPump0(name + suff0, true),   // allow tweaking the pump instance name
495        mPump1(name + suff1, true)
496    {}
497    /// request pump 0's name
498    std::string getName0() const { return mPump0.getName(); }
499    /// request pump 1's name
500    std::string getName1() const { return mPump1.getName(); }
501    /// request both names
502    std::pair<std::string, std::string> getNames() const
503    {
504        return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName());
505    }
506
507    /// request pump 0
508    LLEventPump& getPump0() { return mPump0; }
509    /// request pump 1
510    LLEventPump& getPump1() { return mPump1; }
511
512    /// waitForEventOn(self, either of our two LLEventPumps)
513    template <typename SELF>
514    LLEventWithID wait(SELF& self)
515    {
516        return waitForEventOn(self, mPump0, mPump1);
517    }
518
519    /// errorException(wait(self))
520    template <typename SELF>
521    LLSD waitWithException(SELF& self)
522    {
523        return errorException(wait(self), std::string("Error event on ") + getName1());
524    }
525
526    /// errorLog(wait(self))
527    template <typename SELF>
528    LLSD waitWithLog(SELF& self)
529    {
530        return errorLog(wait(self), std::string("Error event on ") + getName1());
531    }
532
533    template <typename SELF>
534    LLEventWithID postAndWait(SELF& self, const LLSD& event,
535                              const LLEventPumpOrPumpName& requestPump,
536                              const LLSD& replyPump0NamePath=LLSD(),
537                              const LLSD& replyPump1NamePath=LLSD())
538    {
539        return postAndWait2(self, event, requestPump, mPump0, mPump1,
540                            replyPump0NamePath, replyPump1NamePath);
541    }
542
543    template <typename SELF>
544    LLSD postAndWaitWithException(SELF& self, const LLSD& event,
545                                  const LLEventPumpOrPumpName& requestPump,
546                                  const LLSD& replyPump0NamePath=LLSD(),
547                                  const LLSD& replyPump1NamePath=LLSD())
548    {
549        return errorException(postAndWait(self, event, requestPump,
550                                          replyPump0NamePath, replyPump1NamePath),
551                              std::string("Error event on ") + getName1());
552    }
553
554    template <typename SELF>
555    LLSD postAndWaitWithLog(SELF& self, const LLSD& event,
556                            const LLEventPumpOrPumpName& requestPump,
557                            const LLSD& replyPump0NamePath=LLSD(),
558                            const LLSD& replyPump1NamePath=LLSD())
559    {
560        return errorLog(postAndWait(self, event, requestPump,
561                                    replyPump0NamePath, replyPump1NamePath),
562                        std::string("Error event on ") + getName1());
563    }
564
565private:
566    LLEventStream mPump0, mPump1;
567};
568
569#endif /* ! defined(LL_LLEVENTCORO_H) */