If two domains are collaborating and running concurrently there are likely to be times when one must wait until the other has come to some point in its work. I assume shared RAM here for that is often where much of their mutual state lives. One scheme is for each domain to own a cell in shared RAM where it can leave a note to the other when it is blocked. It thereupon becomes ready.

Each domain examines the other’s cell after it has taken any action that might unblock the other. When a domain sees that the other has left a note, it zaps the note and forks the other domain. To reveal bugs it is probably wise to note when the other domain is waiting before beginning to wait yourself. The following sequence in collaborating domains X and Y is to be avoided:

To shift gears I will talk of exclusive locks. The two domains share a wait object for lock conflicts. An exclusive lock is a cell in RAM. The values in the lock are codes for: Compare and swap is used to access the lock. The following is a magic definition of compare-and-swap which is good enough for this note. If lock holds v then CS(int v, b, int * lock) puts b in the lock and returns -1. Otherwise cs returns the value in the lock.
int CS(int v, b, int * lock){ // This block is magically atomic.
  if(v == *lock) {*lock = b; return -1;}
  return *lock;}

When X wants the lock it does:

z: if(CS(0, 1, &lock) != -1){ // conflict
     if(CS(2, 4, &lock) != -1) goto z;
     y: Wait on wait object.
     Set wait object’s time value to next year.
     if(lock == 4) {log("its probably been a year"); goto y;}
     go to z;} 
When X is done with the lock it does:
z: if(CS(1, 0, &lock) != -1){ // Y wants this lock.
     if(CS(4, 0, &lock) != -1) crash;
     Set wait object’s time value to 0 (long past).
Mutatis mutandis for Y. The somewhat costly invocation of the wait object happens only on lock conflict, not on typical lock usage. A correctness proof might note that This code is admittedly delicate. It needs a version with many sharers.