Although the previous section was a bit too much technical the major information was that the blocking operations can cause a preemption and both the Core and the application can run in the meantime. In particular object states after the blocking operation can be different from the states before the operation. This sections describes potential pitfalls.
![]() | Note |
---|---|
A preemption can occur due to a blocking operation only. |
When the Core runs all Core subsystems run too. This means that objects can migrate or be garbage collected, for example. Object replicas can be deleted or become inconsistent. In order to prevent such a behavior objects must be strong referenced from the stack. Note that this does not work for object replicas and one has to be very careful when working with them. The following list summarizes common errors:
Weak-referenced objects can no longer be accessible or consistent
Weak-referenced objects accessible and consistent before the blocking operation can no longer be accessible or can be inconsistent when the operation finishes. Use strong stack pointers to "pin" local (non-replica) objects, copy replica state before issuing a blocking operation.
class RemoteObject : public Object
{
...
void rpc_method();
...
};
class MyObject : public Object
{
...
void f( WeakPointer< MyObject > next );
...
PRemote< RemoteObject > ptr;
};
/*
void MyObject::f( WeakPointer< MyObject > next )
{
next->ptr->sync_rpc_method();
next->ptr = null; /* May fail. */
}
*/
void MyObject::f( WeakPointer< MyObject > next )
{
Pointer< MyObject > n = next;
n->ptr->sync_rpc_method();
n->ptr = null;
} |
Object can be reentered while waiting for a blocking operation completion
Currently the Core ensures that SRPC (the only allowed blocking operation) will never cause a deadlock because the "blocked" objects waiting for the reply are not locked and can be reentered. Application logic must deal with this.
class Counter : public Object
{
...
void add( int count );
...
PInt32 counter;
PRemote< RemoteObject > ptr;
};
/*
void Counter::add( int count )
{
int c = counter;
ptr->sync_rpc_method();
counter = c + count; /* counter may not be equal to c. */
}
*/
void Counter::add( int count )
{
int c = counter;
counter = c + count;
ptr->sync_rpc_method();
} |
Object state can change during a blocking operation
Any object (including the blocked one) can be reentered while a blocking operation is in progress. There may be multiple pending blocking operations in progress. To deal with this problem (if it is a problem at all) either "recheck" the state when the blocking operation finishes, perform such operations at the end of the upcall or implement own "locking" mechanisms on the application level (reject or queue operations submitted in the meantime).
class Operation : public ValueType { ... }; class Operations : public Object { public: ... void do_operation( const Operation & operation ); ... private: ... void do_operation_internal( const Operation & operation ); ... PArray< Operation > retry_queue; PBool inside_do_operation; PRemote< RemoteObject > ptr; }; void Operations::do_operation_internal( const & operation ) { ... /* Blocking operation is performed here. */ ... } void Operations::do_operation( const & operation ) { int i; /* Test for recursion ("lock"). */ if( inside_do_operation ) { retry_queue.push_back( operation ); /* We do not want to perform the operation now. Queue or ignore it. */ return; } /* Do operation. */ inside_do_operation = true; do_operation_internal( operation ); /* Replay queued operations ("unlock"). */ for( i = 0; i < retry_queue.size(); i++ ) { do_operation_internal( retry_queue[ i ] ); /* retry_queue may have grown in the meantime. */ } retry_queue.clear(); inside_do_operation = false; } |
Maybe you should look at the implementation of sector tile reservations in the Demo. In order to move an entity, the corresponding tiles the entity would be occupying must be reserved. The reservation is done using SRPC. If another move request arrives while the previous move is still in progress the entity remembers the new position and once the previous move finishes the new move to the new position is automatically initialized. This differs from the example above in that way that entities remember the last requests only. There is also OPTIMISTIC_LOCK() macro that simplifies the accounting.
![]() | Warning |
---|---|
This is the price for using SRPC. The Core does not implement builtin synchronization primitives to be used by the distributed application because of several reasons:
|