Exemple #1
0
/*----------------------------------------------------------------------
 A change happened in one of the tables the user was monitoring.
 Inform them.
----------------------------------------------------------------------*/
dp_result_t dpSendObjectDelta(
	dp_t *dp,
	dp_result_t status,
	dp_object_t *data,
	dptab_table_t *tab,
	char *subkey,
	int subkeylen)
{
	playerHdl_t dest;
	size_t pktlen;
	size_t datalen;
	dp_result_t err;
	struct {
		dp_packetType_t   tag PACK;
 		dp_objectDelta_packet_t body PACK;
	} pkt;

	precondition(dp);
	precondition(data);
	precondition(tab);
	precondition(subkeylen);

	dp_assertValid(dp);

	memset(&pkt, 0, sizeof(pkt));

	/* DPRINT(("dpSendObjectDeltaPacket: status e:%d, key %s, subkey %s, name ",status, key2a(tab->key, tab->keylen), key2a2(subkey, subkeylen))); */
	DPRINT(("dpSendObjectDeltaPacket: status e:%d, ",status));
	switch(tab->key[0]) {
	case dp_KEY_PLAYERS: {
		if (tab == dp->players) {
			dpid_t id = (dpid_t) dpMAKESHORT(subkey[0], subkey[1]);
			if (dp->monitor_player_latencies) {
				playerHdl_t h = dpid2commHdl(dp, id);

				if (h != PLAYER_NONE) {
					/* Stuff latency into delta for players in our session */
					int		loss;
					int		latency;

					latency = dpio_get_player_latency(dp->dpio, h, 0, &loss);
					if (latency > 32767)
						latency = 32767;

					pkt.body.latency = latency;
					pkt.body.pktloss = loss;
				}
			}

			pkt.body.flags |= dp_OBJECTDELTA_FLAG_INOPENSESS;
			if (id >= dp->firstId && id < dp->firstId + dp_PLAYERS_PER_HOST)
				pkt.body.flags |= dp_OBJECTDELTA_FLAG_LOCAL;
			if (dp_commHdl2dpid(dp, dp->hMaster) == id) 
				pkt.body.flags |= dp_OBJECTDELTA_FLAG_ISHOST;
		}
		datalen = sizeof(dp_playerId_t);
		DPRINT(("playername:%s ping:%d loss:%d", data->p.name, pkt.body.latency, pkt.body.pktloss));
		break; 
	}
	case dp_KEY_SESSIONS:
		if (dp->players && !memcmp(data->sess.reserved2, dp->sess_subkey, dp->sess_subkeylen))
			pkt.body.flags |= dp_OBJECTDELTA_FLAG_INOPENSESS;
		if (!memcmp(data->sess.adrMaster, dp->dpio->myAdr, dp->dpio->myAdrLen))
			pkt.body.flags |= dp_OBJECTDELTA_FLAG_LOCAL;
		datalen = sizeof(dp_session_t); 
		DPRINT(("sessname %s, flags %x", data->sess.sessionName, data->sess.flags));
		break; 
	case dp_KEY_SERVERPINGS:
		datalen = sizeof(dp_serverInfo_t);
		DPRINT(("hostname %s", data->serv.hostname));
		break; 
	case dp_KEY_SCORES:
		datalen = sizeof(data->score.nScoreTypes) 
			+ data->score.nScoreTypes * ( sizeof(data->score.scoreIds[0]) + 
										  sizeof(data->score.scores[0]) );
		DPRINT(("uid %d", dpMAKELONG(subkey[0], subkey[1], subkey[2], subkey[3])));
		break;
	default:
		return dp_RES_BAD;
	}
	DPRINT(("\n"));

	pkt.tag = dp_OBJECTDELTA_PACKET_ID;
	pkt.body.status = status;
	pkt.body.keylen = tab->keylen;
	memcpy(pkt.body.key, tab->key, tab->keylen);
	pkt.body.subkeylen = subkeylen;
	memcpy(pkt.body.subkey, subkey, subkeylen);
	memcpy(&pkt.body.data, data, datalen);

	dest = PLAYER_ME;
	pktlen = sizeof(pkt) - sizeof(pkt.body.data) + datalen;

	err = dpio_put_reliable(dp->dpio, &dest, 1, &pkt, pktlen, NULL);
	dp_assertValid(dp);
	return err;
}
Exemple #2
0
dp_result_t pv_update(pv_t *pv, dpid_t owner)
{
	size_t len, hdrlen;
	pv_peer_t *peer;
	assoctab_item_t *pe;
	pv_var_t *pvar;
	dp_result_t err;
	playerHdl_t errHdl;
	time_t interval;
	int i, j;
	char buf[dpio_MAXLEN_RELIABLE];

	if (!pv) {
		DPRINT(("pv_update: pv null\n"));
		return dp_RES_BUG;
	}

	/* Wait 'til previous transmission has had time to get sent. */
	if ((long)(pv->dp->now - pv->next_send) < 0) return dp_RES_OK;
	/* Set default next-check-time if no transmission this time. */
	pv->next_send = pv->dp->now + pv->dp->clocksPerSec / 8;

	/* To propagate the given id's variables, need to get access to them */
	peer = (pv_peer_t *)assoctab_subscript(pv->peers, owner);
	if (!peer) {
		/*DPRINT(("pv_update: no variables for player %d\n", owner)); */
		return dp_RES_OK;
	}
	/*DPRINT(("pv_update: peer->dirty is %d for player %d\n", peer->dirty, owner));*/

	/* If it's time to start a new cycle, do it. */
	if (pv->cur_key_index == -1) {
		if (pv->new_ndests > 0) {
			/* New hosts have been added since last cycle. */
			/* Start a host update cycle. */

			/* Copy new host list. */
			pv->cur_ndests = pv->new_ndests;
			memcpy(pv->cur_dests, pv->new_dests, pv->new_ndests * sizeof(pv->new_dests[0]));
			/* Reset new host list. */
			pv->new_ndests = 0;

			/* Fill key list with all public keys for this player. */
			for (i=j=0; i<peer->vars->n_used && j<dp_PLAYERDATA_NKEYS_MAX; i++) {
				pe = assoctab_getkey(peer->vars, i);
				if (!pe) break;	/* horrible error */
				pvar = (pv_var_t *) &pe->value;
				if (!(pvar->flags & dp_PLAYERDATA_FLAG_NOFLOOD)) {
					pv->cur_keys[j++] = pe->key;
				}
			}
			/* Early exit if no public variables. */
			if (j == 0) return dp_RES_OK;
			pv->cur_nkeys = j;
			DPRINT(("pv_update: starting new host update cycle. nkeys %d, ndests %d\n", pv->cur_nkeys, pv->cur_ndests));
			/* Trigger start. */
			pv->cur_key_index = 0;
			pv->cur_offset = 0;
		} else if (peer->dirty > 0) {
			/* Varible values have changed since last cycle. */
			/* Start a variable update cycle. */
			peer->dirty = FALSE;
			DPRINT(("pv_update: Clearing peer->dirty for player %d\n", owner));

			/* Set host list to 'all other hosts in game'. */
			pv->cur_ndests = dp_getBroadcastHdls(pv->dp, pv->cur_dests);

			/* Fill key list with all dirty variables.  Mark them clean. */
			for (i=j=0; i<peer->vars->n_used && j<dp_PLAYERDATA_NKEYS_MAX; i++) {
				pe = assoctab_getkey(peer->vars, i);
				if (!pe) break;	/* horrible error */
				pvar = (pv_var_t *) &pe->value;
				if ((pvar->flags & dp_PLAYERDATA_FLAG_DIRTY)
				&&  !(pvar->flags & dp_PLAYERDATA_FLAG_NOFLOOD)) {
					pv->cur_keys[j++] = pe->key;
					pvar->flags &= ~dp_PLAYERDATA_FLAG_DIRTY;
				}
			}
			/* Early exit if no other hosts in game (after clearing dirty!). */
			if (pv->cur_ndests <= 0) return dp_RES_OK;
			/* Early exit if no dirty public variables. */
			if (j == 0) return dp_RES_OK;
			pv->cur_nkeys = j;
			DPRINT(("pv_update: starting new variable update cycle. nkeys %d, ndests %d\n", pv->cur_nkeys, pv->cur_ndests));
			/* Trigger start. */
			pv->cur_key_index = 0;
			pv->cur_offset = 0;
		}
	}

	/* Are we in the middle of an update? */
	if (pv->cur_key_index == -1) return dp_RES_OK;	/* No. */

	/* Get a pointer to the variable we're working on.  Make sure it's
	 * still there, still clean, and still needs data transferred.
	 */
	do {
		pvar = (pv_var_t *)assoctab_subscript(peer->vars, pv->cur_keys[pv->cur_key_index]);
		/* Has it been deleted or changed or finished? */
		if (!pvar
		|| (pvar->flags & dp_PLAYERDATA_FLAG_DIRTY)
		|| (pvar->len <= pv->cur_offset)) {
			DPRINT(("pv_update: pvar %p, flags %x; advancing to next var\n", pvar, pvar?pvar->flags:0x666));
			/* Yes.  Just advance to the next variable. */
			pv->cur_key_index++;
			pv->cur_offset = 0;
			if (pv->cur_key_index >= pv->cur_nkeys) {
				/* That was the last variable.  The cycle is over. */
				pv->cur_key_index = -1;
				DPRINT(("pv_update: cycle over\n"));
				return dp_RES_OK;
			}
			pvar = NULL;
		}
	} while (!pvar);

	/* Send the current chunk of the current variable. */
	if (pv->cur_offset == 0) {
		dp_packetType_t *tag = (dp_packetType_t *)buf;
		pv_playerData_initial_packet_t *body = (pv_playerData_initial_packet_t *)(buf + sizeof(dp_packetType_t));
		void *payload = ((char *)body) + sizeof(pv_playerData_initial_packet_t);

		/* Send an initial chunk. */
		len = pvar->len;
		if (len > pv_PLAYERDATA_INITIAL_MAXLEN)
			len = pv_PLAYERDATA_INITIAL_MAXLEN;

		*tag = pv_PLAYERDATA_INITIAL_PACKET_ID;
		body->len = pvar->len;
		body->id = owner;
		body->flags = (short) pvar->flags;
		body->key = pvar->key;
		body->crc = pvar->crc;
		/* call dpSwapPvUpdateInitial to byte swap body */
		dpSwapPvUpdateInitial(body);
		memcpy(payload, pvar->buf, len);

		hdrlen = sizeof(dp_packetType_t)+sizeof(pv_playerData_initial_packet_t);
	} else {
		dp_packetType_t *tag = (dp_packetType_t *)buf;
		char *pastTag = buf + sizeof(dp_packetType_t);
		pv_playerData_body_packet_t *body = (pv_playerData_body_packet_t *)pastTag;
		void *payload = ((char *)body) + sizeof(pv_playerData_body_packet_t);
		len = pvar->len - pv->cur_offset;
		if (len > pv_PLAYERDATA_BODY_MAXLEN)
			len = pv_PLAYERDATA_BODY_MAXLEN;
#ifndef __MWERKS__
		assert(len>0);
#endif
		*tag = pv_PLAYERDATA_BODY_PACKET_ID;
		body->id = owner;
		/* call dpSwapPvUpdate to byte swap body */
		dpSwapPvUpdate(body);
		memcpy(payload, (char *)(pvar->buf)+pv->cur_offset, len);
		hdrlen = sizeof(dp_packetType_t)+sizeof(pv_playerData_body_packet_t);
	}

	err = dpio_put_reliable(pv->dp->dpio, pv->cur_dests, pv->cur_ndests,
		buf, len + hdrlen, &errHdl);

	/* Estimate the bandwidth used by this send. */
	interval = (pv->dp->clocksPerSec * (len + hdrlen)) / pv_BYTES_PER_SEC;
	pv->next_send = pv->dp->now + interval;
	DPRINT(("pv_update: Sent key %d off %d len %d to %d hosts; result %d, errHdl %d; next send in %d tix.\n",
		pv->cur_keys[pv->cur_key_index],
		pv->cur_offset, len, pv->cur_ndests, err, errHdl, interval));

	if (err == dp_RES_OK)
		pv->cur_offset += len;
	else if (err != dp_RES_FULL)
		DPRINT(("pv_update: serious send problem: dpio_put_reliable returned %d\n", err));

	return err;
}
Exemple #3
0
/*-------------------------------------------------------------------------
 Run a single copy of the test.
 Return value is 0 on success.

 If childnum is > 0, this is a slave; otherwise, this is the master.

 Does the following steps:
 1. Initialize dpio and dptab
 2. Create a table
 3. Establish connections.
	a. Open a handle to address and subscribe the table from that peer.
	   Master subscribes as table2, all else as standard table.
	b. Send an "SU" packet to address requesting peer to publish the table to us
 4. Wait for a "SU" packet.
 5. publish the table to whoever sent "SU" to us.
 Loop appropriate number of times, doing:
	 6. Master sends an "IT" packet to next. On receipt of an "IT" packet,
		slaves send an "IT" packet to next. Master waits for "IT" packet.
	    Watch for iteration number in the IT packet.  If this is the time,
		freeze the dpio and dptab, spawn a replacement, and exit using the
		replacement's exit code.
	 7. If childnum == 0, this child is the master:
		c. Set a value in the table.  Use a max hop count of N.
		d. Wait until value appears in table2 (watch the callback).
		e. Print out how long it took to get there.
 Once the correct number of loops is done,
 8. If childnum == 0: Send a QQ packet to next, wait for QQ packet.
    If childnum > 0: Wait until QQ packet recieved, send QQ to next.
 9. Exit.

 N must be 2 or greater.

 For example:

 If N == 2, the launcher executes:
	dptabt wloop.dll 0 2.0.0.0          (host 1 = master)
	dptabt wloop.dll 1 1.0.0.0          (host 2 = slave)
 the sequence of events is
    host step action
    h1   #4  waits for SU
	h2   #3  subscribes to 1 from h1, sends "SU" to h1, waits for "sU"
	h1   #5  Gets "SU", publishes table 1 to h2
	h1   #6  subscribe's h2's table 1 onto table 2, sends "SU" to h2
	h2   #5  Gets "SU", publishes table 1 to h1
 and the variable's trip around the ring looks like this:
    master -> slave -> master again
 The only reason it doesn't continue on to the slave is that the
 max hop count was set to 2.
 (Interestingly, if another host connects to the master's table,
  whether or not they get a copy of the variable depends on whether
  they connect before the variable comes around again (yes), or after (no).
  This is a good reason to never use rings like this in real systems!)

 If N == 4, the launcher executes:
	dptabt wloop.dll 0 2.0.0.0          (host 1 = master)
	dptabt wloop.dll 1 3.0.0.0          (host 2 = slave)
	dptabt wloop.dll 2 4.0.0.0          (host 3 = slave)
	dptabt wloop.dll 3 1.0.0.0          (host 4 = slave)
 the sequence of events is
    host step action
    h1   #4  waits for SU
	h2   #3  subscribes to 1 from next, sends "SU" to next, waits for "SU"
	h3   #3  subscribes to 1 from next, sends "SU" to next, waits for "SU"
	h4   #3  subscribes to 1 from next, sends "SU" to next, waits for "SU"
	h1   #5  Gets "SU" from prev, publishes table 1 to prev
	h1   #6  subscribe's next's table 1 onto table 2, sends "SU" to next
	h2   #5  Gets "SU" from prev, publishes table 1 to prev
	h3   #5  Gets "SU" from prev, publishes table 1 to prev
	h4   #5  Gets "SU" from prev, publishes table 1 to prev
 and the variable's trip around the ring looks like this:
    h1 -> h2 -> h3 -> h4 -> h1 again
-------------------------------------------------------------------------*/
int
run_one_node(
	int childNum,
	char *sNextAdr,
	int loopTotal,
	int endLoopAt)
{
	dptab_t *tab;
	dp_result_t err;
	char key[10];
	char subkey[10];
	dptab_table_t *table;
	dptab_table_t *table2;
	dpio_t *dpio;
	playerHdl_t dest;
	unsigned char adrBuf[dp_MAX_ADR_LEN];
	commInitReq_t commInitReq;
	commScanAddrReq_t		scanReq;
	commScanAddrResp_t		scanResp;
	dp_transport_t dll;
	char nbuf[dpio_MAXLEN_UNRELIABLE];
	char dplogname[200];

	char fname[256];
	int startLoopAt = 0;
	int i;

	/* Set a timeout of 30 seconds */
	signal(SIGTIMER, timer_handler);
	alarm(30);

	results.thisHost = childNum;

	sprintf(fname, LOGNAME, childNum);
	if(loopTotal < 1) {	/* thawing */
		assert ((logFile = fopen(fname, "a")) != NULL);
		fprintf(logFile, "--Thawing--\n");
	} else
		assert ((logFile = fopen(fname, "w")) != NULL);
	#ifdef WIN32
		srand(GetTickCount());
	#endif

	/* 1. Initialize dpio and dptab */
	dpio_now = eclock();
	if(loopTotal < 1) {	/* thawing */
		/* Find our file */
		FILE* thawFile;
		sprintf(fname, FREEZENAME, childNum);
		printf("Node %d: Thawing from file %s.\n", childNum, fname);
		thawFile = fopen(fname, "r");
		assert (thawFile != NULL);

		/* Read everything from our file */
		fread(&startLoopAt,sizeof(int),1,thawFile);
		fread(&loopTotal,sizeof(int),1,thawFile);
		fread(&it_num,sizeof(int),1,thawFile);
		fread(&(results.n_hosts),sizeof(int),1,thawFile);
		sprintf(dplogname, "dpt%d.%d.log", childNum, startLoopAt);
		dp_setLogFname(dplogname);

		err = dpio_create(&dpio, NULL, NULL, &dpio_now, thawFile);
		assert(err == dp_RES_OK);
		err = dpio_thawHdl(dpio, &dest, thawFile);
		assert(err == dp_RES_OK);

		tab = dptab_create(dpio);
		assert(tab);
		err = dptab_thaw(tab, thawFile);
		assert(err == dp_RES_OK);
		fclose(thawFile);

		/* Re-set any callbacks */
		key[0] = 1;
		table = dptab_getTable(tab, key, 1);
		assert(table != NULL);
		err = dptab_setTableCallback(table, table_cb, NULL);
		assert(err == dp_RES_OK);
		if(childNum == 0) {
			key[0] = 2;
			table2 = dptab_getTable(tab, key, 1);
			assert(table != NULL);
			err = dptab_setTableCallback(table2, table_cb, NULL);
			assert(err == dp_RES_OK);
		}
	} else {	/* not thawing */
		sprintf(dplogname, "dpt%d.%d.log", childNum,startLoopAt);
		dp_setLogFname(dplogname);
		printf("Node %d step 1\n", childNum);

		/* Create our dpio */
		memset(&dll, 0, sizeof(dll));
		strcpy(dll.fname, results.driver);
		memset(&commInitReq, 0, sizeof(commInitReq));
		commInitReq.sessionId = childNum + 1;			/* claim our address */
		commInitReq.portnum = childNum + PORT_OFFSET;	/* claim our port */
		commInitReq.reqLen = sizeof(commInitReq_t);
		err = dpio_create(&dpio, &dll, &commInitReq, &dpio_now, NULL);
		assert(err == dp_RES_OK);

		/* Find our next-in-ring */
		scanReq.printable = sNextAdr;
		scanReq.address = adrBuf;
		scanReq.size = sizeof(adrBuf);
		if (!commScanAddr(&scanReq, &scanResp)) {
			printf("Unable to scan next host address %s, err: %d",
					scanReq.printable, scanResp.status);
			assert(FALSE);
		}
		results.packetLoss = dpio->rxDropPercent;

		/* Create our table collection (dptab) */
		tab = dptab_create(dpio);
		assert(tab);

		/* 2. Create tables */
		printf("Node %d step 2\n", childNum);
		key[0] = 1;
		err = dptab_createTable(tab, &table, key, 1, sizeof(int), NULL, NULL, table_cb, NULL);
		assert(err == dp_RES_OK);
		if(childNum == 0) {
			key[0] = 2;
			err = dptab_createTable(tab, &table2, key, 1, sizeof(int), NULL, NULL, table_cb, NULL);
			assert(err == dp_RES_OK);
		}

		/* 3. Establish connections */
		printf("Node %d step 3\n", childNum);
		/*  Open a comm handle to the partner's address */
		dest = dpio_openHdl(dpio, adrBuf, NULL, NULL);
		if (dest == PLAYER_NONE) {
			printf("Unable to connect to address %s", scanReq.printable);
			assert(FALSE);
		}
		printf("Node %d: Opening address %s returns handle %d/%x\n", childNum, scanReq.printable, dest, dest);
		if (dest == PLAYER_ME) {
			printf("Executed out of order - got handle to myself\n");
			assert(FALSE);
		}

		/* Add the next machine to the party */
		err = dptab_addPeer(tab, dest);
		/* only master should have received packet from unknown source. */
		if ((childNum == 0) && (su_src == dest))
			assert(err == dp_RES_ALREADY);
		else
			assert(err == dp_RES_OK);

		/* Let the next machine publish on top of our table */
		key[0] = 1;
		if(childNum == 0)
			err = dptab_addPublisher(tab, table2, key, 1, dest);
		else
			err = dptab_addPublisher(tab, table, key, 1, dest);
		assert(err == dp_RES_OK);

		/* Request the next machine publish table to us */
		(*(dp_packetType_t *)nbuf) = PKT_SU;
		err = dpio_put_reliable(dpio, &dest, 1, nbuf, sizeof(dp_packetType_t)+2, NULL);
		assert(err == dp_RES_OK);
		printf("Node %d sent SU to h:%x\n", childNum, dest);

		/* 4. Wait for an "SU" packet. */
		printf("Node %d step 4: waiting for su.\n", childNum);
		su_src = PLAYER_NONE;
		do {
			poll_test(dpio, tab);
		} while (su_src == PLAYER_NONE);

		/* 5. publish the table to whoever sent "SU" to us. */
		printf("Node %d step 5\n", childNum);
		err = dptab_addPeer(tab, su_src);
		if (su_src == dest)
			assert(err == dp_RES_ALREADY);
		else
			assert(err == dp_RES_OK);
		err = dptab_addSubscriber(tab, table, su_src);
		assert(err == dp_RES_OK);
	}	/* not thawing */

	for(i = startLoopAt; i < loopTotal; i++) {
		/* 6. Send an IT packet around the ring. */
		printf("Node %d step 6.%d\n", childNum, i);
		if(childNum == 0) {	/* Master sends first IT. */
			(*(dp_packetType_t *)nbuf) = PKT_IT;
			nbuf[sizeof(dp_packetType_t)] = (char) (i & 0x7F);
			err = dpio_put_reliable(dpio, &dest, 1, nbuf,
						sizeof(dp_packetType_t)+sizeof(char), NULL);
			assert(err == dp_RES_OK);
		}
		printf("Node %d waiting for IT(%d).\n", childNum, i);
		do {				/* Wait for IT */
			poll_test(dpio, tab);
		} while (((int) it_num) < (i & 0x7F));
		if(childNum != 0) {	/* Slaves send IT after getting one. */
			(*(dp_packetType_t *)nbuf) = PKT_IT;
			nbuf[sizeof(dp_packetType_t)] = (char) i;
			err = dpio_put_reliable(dpio, &dest, 1, nbuf,
						sizeof(dp_packetType_t)+sizeof(char), NULL);
			assert(err == dp_RES_OK);
		}

		/* 7. If master, set a value and wait for it to come back around. */
		if(childNum == 0) {
			printf("Node %d step 7\n", childNum);
			subkey[0] = SUBKEY_SINGLE_SMALL;
			sprintf(nbuf, "a%03d", i);
			printf("Node %d: setting variable to %s\n", childNum, nbuf);
			results.sentSingleSmallTime = dpio_now;
			err = dptab_set(tab, table, subkey, 1, nbuf, strlen(nbuf), results.n_hosts, PLAYER_ME);
			if (err != dp_RES_OK) {
				printf("Node %d: dptab_set returns err %d!\n", childNum, err);
				assert(err == dp_RES_OK);
			}
		}

		/* Freeze and spawn if it's the right iteration. */
		/* Do it here to force an xfer to be saved and restored. */
		if(i == endLoopAt) {
			FILE* freezeFile;
			int result;
			int proc;

			/* Find our file */
			sprintf(fname, FREEZENAME, childNum);
			printf("Node %d: Iteration %d, Freezing to file %s.\n", childNum, i, fname);
			freezeFile = fopen(fname, "w");
			assert (freezeFile != NULL);

			/* Write everything to our file */
			fwrite(&i,sizeof(int),1,freezeFile);
			fwrite(&loopTotal,sizeof(int),1,freezeFile);
			fwrite(&it_num,sizeof(int),1,freezeFile);
			fwrite(&(results.n_hosts),sizeof(int),1,freezeFile);

			err = dpio_freeze(dpio, freezeFile);
			assert(err == dp_RES_OK);
			err = dpio_freezeHdl(dpio, dest, freezeFile);
			assert(err == dp_RES_OK);

			err = dptab_freeze(tab, freezeFile);
			assert(err == dp_RES_OK);
			fclose(freezeFile);
			dptab_destroy(tab);
			dpio_destroy(dpio, 1);

			/* Spawn and return child's return code */
			sprintf(buf, "%d", childNum);
			proc = _spawnl(_P_NOWAIT, results.exe, results.exe, buf, fname, NULL);
			_cwait(&result, proc, 0);
			printf("Node %d.%d returned %d.\n", childNum, i, result);
			return result;
		}

		if(childNum == 0) {
			/* d. Wait until value appears in table2 (callback). */
			printf("Node %d waiting for value to appear.\n", childNum);
			results.gotSingleSmallTime = -1;
			do {
				poll_test(dpio, tab);
			} while (results.gotSingleSmallTime == -1);
		}
	}

	/* 8. Send a QQ packet around the ring. */
	printf("Node %d step 8\n", childNum);
	qq_src = PLAYER_NONE;
	if(childNum == 0) {	/* Master sends first QQ */
		(*(dp_packetType_t *)nbuf) = PKT_QQ;
		err = dpio_put_reliable(dpio, &dest, 1, nbuf, sizeof(dp_packetType_t), NULL);
		assert(err == dp_RES_OK);
	}
	/* Wait for QQ */
	printf("Node %d waiting for QQ.\n", childNum);
	do {
		poll_test(dpio, tab);
	} while (qq_src == PLAYER_NONE);
	if(childNum != 0) {	/* Slaves wait for a QQ and then send QQ */
		(*(dp_packetType_t *)nbuf) = PKT_QQ;
		err = dpio_put_reliable(dpio, &dest, 1, nbuf, sizeof(dp_packetType_t), NULL);
		assert(err == dp_RES_OK);
	}

	/* 9. Exit. */
	printf("Node %d step 9\n", childNum);
	/* Ack packets for four more seconds just in case */
	{
		clock_t start = eclock();
		while ((long)(eclock() - start) < 4 * ECLOCKS_PER_SEC) {
			dpio_now = eclock();
			dpio_update(dpio);
		}
	}
	printf("Node %d exiting\n", childNum);
	return 0;
}