summaryrefslogtreecommitdiffstats
path: root/flow/asyncschedule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'flow/asyncschedule.cpp')
-rw-r--r--flow/asyncschedule.cpp553
1 files changed, 553 insertions, 0 deletions
diff --git a/flow/asyncschedule.cpp b/flow/asyncschedule.cpp
new file mode 100644
index 0000000..66ae48b
--- /dev/null
+++ b/flow/asyncschedule.cpp
@@ -0,0 +1,553 @@
+ /*
+
+ Copyright (C) 2000,2001 Stefan Westerfeld
+ stefan@space.twc.de
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ */
+
+#include <iostream>
+#include "asyncschedule.h"
+
+using namespace std;
+using namespace Arts;
+
+#include "debug.h"
+#include <stdio.h>
+
+/* Since this file is a tad bit more complex, here is some basic documentation:
+
+1) ASyncPort: There are asynchronous ports which are parts of the standard-
+ flowsystem schedule nodes. Their lifetime starts whenever an asynchronous
+ stream gets created by the flow system, and ends when the schedule node
+ gets destroyed. Basically, an ASyncPort has two functions:
+
+ * it is a "Port", which means that it gets connect(), disconnect() and
+ other calls from the flowsystem
+
+ * it is a "GenericDataChannel", which means that DataPackets can interact
+ with it
+
+ Although there will be ASyncPorts which only send data and ASyncPorts which
+ only receive data (there are none that do both), there are no distinct
+ classes for this.
+
+2) Standard case: a DataPacket that gets transported over a datachannel locally:
+
+ 1. the user allocates himself a datapacket "packet"
+ 2. the user calls "packet->send()", which in turn calls
+ ASyncPort::sendPacket(packet)
+ 3. the ASyncPort sends the DataPacket to every subscriber (incrementing the
+ useCount) over the NotificationManager
+ 4. the NotificationManager delivers the DataPackets to the receiver
+ 5. eventually, the receiver confirms using "packet->processed()"
+ 6. the packet informs the ASyncPort::processedPacket()
+ 7. the packet is freed
+
+variant (pulling):
+
+ 1. the user gets told by the ASyncPort: produce some data, here is a "packet"
+ 2. the user calls "packet->send()", which in turn calls
+ ASyncPort::sendPacket(packet)
+ 3. the ASyncPort sends the DataPacket to every subscriber (incrementing the
+ useCount) over the NotificationManager
+ 4. the NotificationManager delivers the DataPackets to the receiver
+ 5. eventually, the receiver confirms using "packet->processed()"
+ 6. the packet informs the ASyncPort::processedPacket()
+ 7. the ASyncPort restarts with 1.
+
+3) Remote case: the remote case follows from the local case by adding two extra
+things: one object that converts packets from their packet form to a message
+(ASyncNetSend), and one object that converts packets from the message form
+to a packet again. Effectively, the sending of a single packet looks like
+this, then:
+
+ 1-S. the user allocates himself a datapacket "packet"
+ 2-S. the user calls "packet->send()", which in turn calls
+ ASyncPort::sendPacket(packet)
+ 3-S. the ASyncPort sends the DataPacket to every subscriber (incrementing the
+ useCount) over the NotificationManager
+ 4-S. the NotificationManager delivers the DataPackets to the ASyncNetSend
+ 5-S. the ASyncNetSend::notify method gets called, which in turn converts
+ the packet to a network message
+
+ ... network transfer ...
+
+ 6-R. the ASyncNetReceive::receive method gets called - the method creates
+ a new data packet, and sends it using the NotificationManager again
+ 7-R. the NotificationManager delivers the DataPacket to the receiver
+ 8-R. eventually, the receiver confirms using "packet->processed()"
+ 9-R. the packet informs the ASyncNetReceive::processedPacket() which
+ frees the packet and tells the (remote) sender that it went all right
+
+ ... network transfer ...
+
+ 10-S. eventually, ASyncNetSend::processed() gets called, and confirms
+ the packet using "packet->processed()"
+ 11-S. the packet informs the ASyncPort::processedPacket()
+ 12-S. the packet is freed
+
+variant(pulling):
+
+ works the same as in the local case by exchanging steps 1-S and 12-S
+
+4) ownership:
+
+ * ASyncPort: is owned by the Object which it is a part of, if the object
+ dies, ASyncPort dies unconditionally
+
+ * DataPacket: is owned by the GenericDataChannel they are propagated over,
+ that is, the ASyncPort normally - however if the DataPacket is still in
+ use (i.e. in state 5 of the local case), it will take responsibility to
+ free itself once all processed() calls have been collected
+
+ * ASyncNetSend, ASyncNetReceive: own each other, so that if the sender dies,
+ the connection will die as well, and if the receiver dies, the same happens
+
+*/
+
+#undef DEBUG_ASYNC_TRANSFER
+
+ASyncPort::ASyncPort(const std::string& name, void *ptr, long flags,
+ StdScheduleNode* parent) : Port(name, ptr, flags, parent), pull(false)
+{
+ stream = (GenericAsyncStream *)ptr;
+ stream->channel = this;
+ stream->_notifyID = notifyID = parent->object()->_mkNotifyID();
+}
+
+ASyncPort::~ASyncPort()
+{
+ /*
+ * tell all outstanding packets that we don't exist any longer, so that
+ * if they feel like they need to confirm they have been processed now,
+ * they don't talk to an no longer existing object about it
+ */
+ while(!sent.empty())
+ {
+ sent.front()->channel = 0;
+ sent.pop_front();
+ }
+
+ /* disconnect remote connections (if present): the following things will
+ * need to be ensured here, since we are being deleted:
+ *
+ * - the senders should not talk to us after our destructor
+ * - all of our connections need to be disconnected
+ * - every connection needs to be closed exactly once
+ *
+ * (closing a connection can cause reentrancy due to mcop communication)
+ */
+ while(!netSenders.empty())
+ netSenders.front()->disconnect();
+
+ FlowSystemReceiver receiver = netReceiver;
+ if(!receiver.isNull())
+ receiver.disconnect();
+}
+
+//-------------------- GenericDataChannel interface -------------------------
+
+void ASyncPort::setPull(int packets, int capacity)
+{
+ pullNotification.receiver = parent->object();
+ pullNotification.ID = notifyID;
+ pullNotification.internal = 0;
+ pull = true;
+
+ for(int i=0;i<packets;i++)
+ {
+ GenericDataPacket *packet = stream->createPacket(capacity);
+ packet->useCount = 0;
+ pullNotification.data = packet;
+ NotificationManager::the()->send(pullNotification);
+ }
+}
+
+void ASyncPort::endPull()
+{
+ pull = false;
+ // TODO: maybe remove all pending pull packets here
+}
+
+void ASyncPort::processedPacket(GenericDataPacket *packet)
+{
+ int count = 0;
+ list<GenericDataPacket *>::iterator i = sent.begin();
+ while(i != sent.end())
+ {
+ if(*i == packet)
+ {
+ count++;
+ i = sent.erase(i);
+ }
+ else i++;
+ }
+ assert(count == 1);
+
+#ifdef DEBUG_ASYNC_TRANSFER
+ cout << "port::processedPacket" << endl;
+#endif
+ assert(packet->useCount == 0);
+ if(pull)
+ {
+ pullNotification.data = packet;
+ NotificationManager::the()->send(pullNotification);
+ }
+ else
+ {
+ stream->freePacket(packet);
+ }
+}
+
+void ASyncPort::sendPacket(GenericDataPacket *packet)
+{
+ bool sendOk = false;
+
+#ifdef DEBUG_ASYNC_TRANSFER
+ cout << "port::sendPacket" << endl;
+#endif
+
+ if(packet->size > 0)
+ {
+ vector<Notification>::iterator i;
+ for(i=subscribers.begin(); i != subscribers.end(); i++)
+ {
+ Notification n = *i;
+ n.data = packet;
+ packet->useCount++;
+#ifdef DEBUG_ASYNC_TRANSFER
+ cout << "sending notification " << n.ID << endl;
+#endif
+ NotificationManager::the()->send(n);
+ sendOk = true;
+ }
+ }
+
+ if(sendOk)
+ sent.push_back(packet);
+ else
+ stream->freePacket(packet);
+}
+
+//----------------------- Port interface ------------------------------------
+
+void ASyncPort::connect(Port *xsource)
+{
+ arts_debug("port(%s)::connect",_name.c_str());
+
+ ASyncPort *source = xsource->asyncPort();
+ assert(source);
+ addAutoDisconnect(xsource);
+
+ Notification n;
+ n.receiver = parent->object();
+ n.ID = notifyID;
+ n.internal = 0;
+ source->subscribers.push_back(n);
+}
+
+void ASyncPort::disconnect(Port *xsource)
+{
+ arts_debug("port::disconnect");
+
+ ASyncPort *source = xsource->asyncPort();
+ assert(source);
+ removeAutoDisconnect(xsource);
+
+ // remove our subscription from the source object
+ vector<Notification>::iterator si;
+ for(si = source->subscribers.begin(); si != source->subscribers.end(); si++)
+ {
+ if(si->receiver == parent->object())
+ {
+ source->subscribers.erase(si);
+ return;
+ }
+ }
+
+ // there should have been exactly one, so this shouldn't be reached
+ assert(false);
+}
+
+ASyncPort *ASyncPort::asyncPort()
+{
+ return this;
+}
+
+GenericAsyncStream *ASyncPort::receiveNetCreateStream()
+{
+ return stream->createNewStream();
+}
+
+NotificationClient *ASyncPort::receiveNetObject()
+{
+ return parent->object();
+}
+
+long ASyncPort::receiveNetNotifyID()
+{
+ return notifyID;
+}
+
+// Network transparency
+void ASyncPort::addSendNet(ASyncNetSend *netsend)
+{
+ Notification n;
+ n.receiver = netsend;
+ n.ID = netsend->notifyID();
+ n.internal = 0;
+ subscribers.push_back(n);
+ netSenders.push_back(netsend);
+}
+
+void ASyncPort::removeSendNet(ASyncNetSend *netsend)
+{
+ arts_return_if_fail(netsend != 0);
+ netSenders.remove(netsend);
+
+ vector<Notification>::iterator si;
+ for(si = subscribers.begin(); si != subscribers.end(); si++)
+ {
+ if(si->receiver == netsend)
+ {
+ subscribers.erase(si);
+ return;
+ }
+ }
+ arts_warning("Failed to remove ASyncNetSend (%p) from ASyncPort", netsend);
+}
+
+void ASyncPort::setNetReceiver(ASyncNetReceive *receiver)
+{
+ arts_return_if_fail(receiver != 0);
+
+ FlowSystemReceiver r = FlowSystemReceiver::_from_base(receiver->_copy());
+ netReceiver = r;
+}
+
+void ASyncPort::disconnectRemote(const string& dest)
+{
+ list<ASyncNetSend *>::iterator i;
+
+ for(i = netSenders.begin(); i != netSenders.end(); i++)
+ {
+ if((*i)->dest() == dest)
+ {
+ (*i)->disconnect();
+ return;
+ }
+ }
+ arts_warning("failed to disconnect %s in ASyncPort", dest.c_str());
+}
+
+ASyncNetSend::ASyncNetSend(ASyncPort *ap, const std::string& dest) : ap(ap)
+{
+ _dest = dest;
+ ap->addSendNet(this);
+}
+
+ASyncNetSend::~ASyncNetSend()
+{
+ while(!pqueue.empty())
+ {
+ pqueue.front()->processed();
+ pqueue.pop();
+ }
+ if(ap)
+ {
+ ap->removeSendNet(this);
+ ap = 0;
+ }
+}
+
+long ASyncNetSend::notifyID()
+{
+ return 1;
+}
+
+void ASyncNetSend::notify(const Notification& notification)
+{
+ // got a packet?
+ assert(notification.ID == notifyID());
+ GenericDataPacket *dp = (GenericDataPacket *)notification.data;
+ pqueue.push(dp);
+
+ /*
+ * since packets are delivered asynchronously, and since disconnection
+ * involves communication, it might happen that we get a packet without
+ * actually being connected any longer - in that case, silently forget it
+ */
+ if(!receiver.isNull())
+ {
+ // put it into a custom data message and send it to the receiver
+ Buffer *buffer = receiver._allocCustomMessage(receiveHandlerID);
+ dp->write(*buffer);
+ receiver._sendCustomMessage(buffer);
+ }
+}
+
+void ASyncNetSend::processed()
+{
+ assert(!pqueue.empty());
+ pqueue.front()->processed();
+ pqueue.pop();
+}
+
+void ASyncNetSend::setReceiver(FlowSystemReceiver newReceiver)
+{
+ receiver = newReceiver;
+ receiveHandlerID = newReceiver.receiveHandlerID();
+}
+
+void ASyncNetSend::disconnect()
+{
+ /* since disconnection will cause destruction (most likely immediate),
+ * we'll reference ourselves ... */
+ _copy();
+
+ if(!receiver.isNull())
+ {
+ FlowSystemReceiver r = receiver;
+ receiver = FlowSystemReceiver::null();
+ r.disconnect();
+ }
+ if(ap)
+ {
+ ap->removeSendNet(this);
+ ap = 0;
+ }
+
+ _release();
+}
+
+string ASyncNetSend::dest()
+{
+ return _dest;
+}
+
+/* dispatching function for custom message */
+
+static void _dispatch_ASyncNetReceive_receive(void *object, Buffer *buffer)
+{
+ ((ASyncNetReceive *)object)->receive(buffer);
+}
+
+ASyncNetReceive::ASyncNetReceive(ASyncPort *port, FlowSystemSender sender)
+{
+ port->setNetReceiver(this);
+ stream = port->receiveNetCreateStream();
+ stream->channel = this;
+ this->sender = sender;
+ /* stream->_notifyID = _mkNotifyID(); */
+
+ gotPacketNotification.ID = port->receiveNetNotifyID();
+ gotPacketNotification.receiver = port->receiveNetObject();
+ gotPacketNotification.internal = 0;
+ _receiveHandlerID =
+ _addCustomMessageHandler(_dispatch_ASyncNetReceive_receive,this);
+}
+
+ASyncNetReceive::~ASyncNetReceive()
+{
+ /* tell outstanding packets that we don't exist any longer */
+ while(!sent.empty())
+ {
+ sent.front()->channel = 0;
+ sent.pop_front();
+ }
+ delete stream;
+}
+
+long ASyncNetReceive::receiveHandlerID()
+{
+ return _receiveHandlerID;
+}
+
+void ASyncNetReceive::receive(Buffer *buffer)
+{
+ GenericDataPacket *dp = stream->createPacket(512);
+ dp->read(*buffer);
+ dp->useCount = 1;
+ gotPacketNotification.data = dp;
+ NotificationManager::the()->send(gotPacketNotification);
+ sent.push_back(dp);
+}
+
+/*
+ * It will happen that this routine is called in time critical situations,
+ * such as: while audio calculation is running, and must be finished in
+ * time. The routine is mostly harmless, because sender->processed() is
+ * a oneway function, which just queues the buffer for sending and returns
+ * back, so it should return at once.
+ *
+ * However there is an exception upon first call: when sender->processed()
+ * is called for the first time, the method processed has still to be looked
+ * up. Thus, a synchronous call to _lookupMethod is made. That means, upon
+ * first call, the method will send out an MCOP request and block until the
+ * remote process tells that id.
+ */
+void ASyncNetReceive::processedPacket(GenericDataPacket *packet)
+{
+ /*
+ * HACK! Upon disconnect, strange things will happen. One of them is
+ * that we might, for the reason of not being referenced any longer,
+ * cease to exist without warning. Another is that our nice "sender"
+ * reference will get a null reference without warning, see disconnect
+ * code (which will cause the attached stub to also disappear). As
+ * those objects (especially the stub) are not prepared for not
+ * being there any more in the middle of whatever they do, we here
+ * explicitly reference us, and them, *again*, so that no evil things
+ * will happen. A general solution for this would be garbage collection
+ * in a timer, but until this is implemented (if it ever will become
+ * implemented), we'll live with this hack.
+ */
+ _copy();
+ sent.remove(packet);
+ stream->freePacket(packet);
+ if(!sender.isNull())
+ {
+ FlowSystemSender xsender = sender;
+ xsender.processed();
+ }
+ _release();
+}
+
+void ASyncNetReceive::disconnect()
+{
+ if(!sender.isNull())
+ {
+ FlowSystemSender s = sender;
+ sender = FlowSystemSender::null();
+ s.disconnect();
+ }
+}
+
+void ASyncNetReceive::sendPacket(GenericDataPacket *)
+{
+ assert(false);
+}
+
+void ASyncNetReceive::setPull(int, int)
+{
+ assert(false);
+}
+
+void ASyncNetReceive::endPull()
+{
+ assert(false);
+}