static void JoinSetMode (int mode) { joinGlobals.joinMode = mode; # if DebugJoin<=8 sprintf(cBuffer, "%4d: JoinMode=%s.\n", me, JoinModeName(joinGlobals.joinMode)); DDD_PrintDebug(cBuffer); # endif }
void DDD_JoinObj (DDD_HDR hdr, DDD_PROC dest, DDD_GID new_gid) { JIJoin *ji; if (!ddd_JoinActive()) { DDD_PrintError('E', 7012, "Missing DDD_JoinBegin(). aborted"); HARD_EXIT; } if (dest>=procs) { sprintf(cBuffer, "cannot join %08x with %08x on processor %d (procs=%d)", OBJ_GID(hdr), new_gid, dest, procs); DDD_PrintError('E', 7003, cBuffer); HARD_EXIT; } if (dest==me) { sprintf(cBuffer, "cannot join %08x with myself", OBJ_GID(hdr)); DDD_PrintError('E', 7004, cBuffer); HARD_EXIT; } if (ObjHasCpl(hdr)) { sprintf(cBuffer, "cannot join %08x, object already distributed", OBJ_GID(hdr)); DDD_PrintError('E', 7005, cBuffer); HARD_EXIT; } ji = JIJoinSet_NewItem(joinGlobals.setJIJoin); ji->hdr = hdr; ji->dest = dest; ji->new_gid = new_gid; if (! JIJoinSet_ItemOK(joinGlobals.setJIJoin)) return; # if DebugJoin<=2 sprintf(cBuffer, "%4d: DDD_JoinObj %08x, dest=%d, new_gid=%08x\n", me, OBJ_GID(hdr), dest, new_gid); DDD_PrintDebug(cBuffer); # endif }
RETCODE XferPackMsgs (XFERMSG *theMsgs) { XFERMSG *xm; #if DebugPack<=3 sprintf(cBuffer, "%d: XferPackMsgs\n", me); DDD_PrintDebug(cBuffer); fflush(stdout); #endif /* sort messages according to decreasing size. i.e., send biggest message first. LowComm will use this to handle situations with little memory ressources. */ { int i, n; XFERMSG **xm_array; /* count number of messages */ for(n=0, xm=theMsgs; xm!=NULL; xm=xm->next) n++; if (n>0) { /* alloc array of pointers to messages */ xm_array = (XFERMSG **) OO_Allocate (sizeof(XFERMSG *) * n); if (xm_array!=NULL) { for(i=0, xm=theMsgs; i<n; xm=xm->next, i++) xm_array[i] = xm; /* sort array and relink list */ qsort(xm_array, n, sizeof(XFERMSG *), sort_MsgSize); theMsgs = xm_array[0]; for(i=0; i<n-1; i++) xm_array[i]->next = xm_array[i+1]; if (n>1) xm_array[n-1]->next = NULL; /* free array */ OO_Free (xm_array /*,0*/); } /* else { resorting msg-list is not possible due to memory shortage. simply don't do it. } */ } } /* allocate buffer, pack messages and send away */ for(xm=theMsgs; xm!=NULL; xm=xm->next) { if (! LC_MsgAlloc(xm->msg_h)) { sprintf(cBuffer, STR_NOMEM " in XferPackMsgs (size=%ld)", (unsigned long) LC_GetBufferSize(xm->msg_h)); DDD_PrintError('E', 6522, cBuffer); RET_ON_ERROR; } XferPackSingleMsg(xm); LC_MsgSend(xm->msg_h); } RET_ON_OK; }
static void XferPackSingleMsg (XFERMSG *msg) { SYMTAB_ENTRY *theSymTab; OBJTAB_ENTRY *theObjTab; TENewCpl *theNewCpl; TEOldCpl *theOldCpl; char *theObjects, *currObj; int i, actSym, actNewCpl, actOldCpl, actObj, recvProc; INT mi; /* recipient of this message */ recvProc = msg->proc; /* get table addresses inside message */ theSymTab = (SYMTAB_ENTRY *)LC_GetPtr(msg->msg_h, xferGlobals.symtab_id); theObjTab = (OBJTAB_ENTRY *)LC_GetPtr(msg->msg_h, xferGlobals.objtab_id); theNewCpl = (TENewCpl *) LC_GetPtr(msg->msg_h, xferGlobals.newcpl_id); theOldCpl = (TEOldCpl *) LC_GetPtr(msg->msg_h, xferGlobals.oldcpl_id); theObjects= (char *)LC_GetPtr(msg->msg_h, xferGlobals.objmem_id); /* build several tables inside message */ actSym = actNewCpl = actOldCpl = actObj = 0; currObj = theObjects; for(i=0; i<msg->nObjItems; i++) /* for all XICopyObj-items */ { REGISTER XICopyObj *xi = msg->xferObjArray[i]; REGISTER DDD_HDR hdr = xi->hdr; TYPE_DESC *desc = &theTypeDefs[OBJ_TYPE(hdr)]; DDD_OBJ obj = HDR2OBJ(hdr,desc); /*COUPLING *cpl;*/ DDD_HDR copyhdr; /* build coupling table */ /* skip cpl which describes object itself (receive proc) */ /* for(cpl=THECOUPLING(hdr); cpl!=NULL; cpl=cpl->next) { if (cpl->proc!=recvProc) { theNewCpl[actNewCpl].gid = OBJ_GID(hdr); theNewCpl[actNewCpl].proc = cpl->proc; theNewCpl[actNewCpl].prio = cpl->prio; actNewCpl++; } } */ /* one coupling for object itself (send proc) */ /* theNewCpl[actNewCpl].gid = OBJ_GID(hdr); theNewCpl[actNewCpl].proc = me; theNewCpl[actNewCpl].prio = OBJ_PRIO(hdr); actNewCpl++; */ #if defined(C_FRONTEND) || defined(CPP_FRONTEND) copyhdr = OBJ2HDR(currObj,desc); #else copyhdr = (DDD_HDR) currObj; #endif /* update object table */ #if defined(C_FRONTEND) || defined(CPP_FRONTEND) theObjTab[actObj].h_offset = (int)(((char *)copyhdr)-theObjects); #else theObjTab[actObj].o_offset = (int)(currObj-theObjects); theObjTab[actObj].typ = OBJ_TYPE(hdr); theObjTab[actObj].gid = OBJ_GID(hdr); theObjTab[actObj].attr = OBJ_ATTR(hdr); theObjTab[actObj].prio = xi->prio; #endif theObjTab[actObj].hdr = NULL; theObjTab[actObj].addLen = xi->addLen; theObjTab[actObj].size = xi->size; /* needed for variable-sized objects */ actObj++; /* copy object into message. in the following xi->size equals desc->len for fixed-size objects. */ /*STAT_RESET3;*/ #if defined(C_FRONTEND) || defined(CPP_FRONTEND) /* NOTE: object memory is copied _completely_, i.e., also LDATA- components are copied into message and sent to destination. then, on the receiving processor the data is sorted out... */ memcpy(currObj, obj, xi->size); #else ObjToMsg (obj, desc, currObj); #endif /*STAT_INCTIMER3(32);*/ /* insert priority into copy */ OBJ_PRIO(copyhdr) = xi->prio; /* call application handler for direct manipulation */ /* KB 941110: moved from objmgr.c */ /* Caution: this is a very, very dirty situation. HANDLER_XFERCOPYMANIP is able to manipulate the obj-copy inside the message. this handler should be removed in future DDD versions. */ #if defined(C_FRONTEND) if (desc->handlerXFERCOPYMANIP) { /* NOTE: OBJ_TYPE could change during the execution of HANDLER_XFERCOPYMANIP. however, the position of DDD_HEADER inside the object should not change. therefore, we can remember the offsetHeader here and use it afterwards to adjust the desc. */ int offset = desc->offsetHeader; /* now call handler */ desc->handlerXFERCOPYMANIP(currObj); /* adjust new description according to new type */ desc = &(theTypeDefs[OBJ_TYPE((DDD_HDR)(currObj+offset))]); } #endif /* build symbol table portion from object copy */ actSym += BuildSymTab(desc, obj, (char *)currObj, &(theSymTab[actSym])); /* advance to next free object slot in message, c.f. alignment */ currObj += CEIL(xi->size); /* gather additional data */ if (xi->addLen>0) { actSym += GetDepData(currObj, desc, obj, &(theSymTab[actSym]), xi); currObj += xi->addLen; } } /* for all XINewCpl items in this message */ for(i=0; i<msg->nNewCpl; i++) { theNewCpl[actNewCpl] = msg->xferNewCpl[i]->te; actNewCpl++; } /* for all XIOldCpl items in this message */ for(i=0; i<msg->nOldCpl; i++) { theOldCpl[actOldCpl] = msg->xferOldCpl[i]->te; actOldCpl++; } /* sort SymTab, ObjTab and CplTab */ /*STAT_RESET3;*/ qsort(theSymTab, actSym, sizeof(SYMTAB_ENTRY), sort_SymTabEntries); /*STAT_INCTIMER3(34); STAT_RESET3;*/ /* sorting of objtab is necessary!! (see AcceptObjFromMsg) KB 960812 */ currentObjectMem = theObjects; qsort(theObjTab, msg->nObjects, sizeof(OBJTAB_ENTRY), sort_ObjTabEntries); /*STAT_INCTIMER3(35); STAT_RESET3;*/ #ifdef SORT_STATISTIK sprintf(cBuffer, "ITEMS 34=%d, 35=%d, 36=%d\n", actSym, msg->nObjects, actNewCpl); DDD_PrintDebug(cBuffer); sprintf(cBuffer, "COMPS 34=%d, 35=%d, 36=%d\n", n34, n35, n36); DDD_PrintDebug(cBuffer); #endif /* substitute all pointers by index into SymTab */ /*TAT_RESET3;*/ for(mi=0; mi<actSym; mi++) { /* patch SymTab index into reference location inside message */ #if defined(C_FRONTEND) || defined(CPP_FRONTEND) *(theSymTab[mi].adr.ref) = (DDD_OBJ)(mi+1); #endif #ifdef F_FRONTEND *(theSymTab[mi].adr.ref) = (mi+1); #endif } /*STAT_INCTIMER3(37);*/ /* TODO: theSymtab[].ref wird ab hier nicht mehr verwendet und muss nicht uebertragen werden! */ /* set valid table entries */ LC_SetTableLen(msg->msg_h, xferGlobals.symtab_id, actSym); LC_SetTableLen(msg->msg_h, xferGlobals.objtab_id, msg->nObjects); LC_SetTableLen(msg->msg_h, xferGlobals.newcpl_id, actNewCpl); LC_SetTableLen(msg->msg_h, xferGlobals.oldcpl_id, actOldCpl); #if DebugXfer>1 if (DDD_GetOption(OPT_DEBUG_XFERMESGS)==OPT_ON) #endif XferDisplayMsg("OS", msg->msg_h); }
void DDD_Library::TypeDefine (DDD_TYPE typ, ...) #endif { TYPE_DESC *desc; size_t argsize; char *argp; int argtyp, argno; DDD_TYPE argrefs; int i, nPtr; char *errtxt; va_list ap; char *adr; char *gbits; #if defined(CPP_FRONTEND) int size; int offset; #endif /* TODO: only master should be able to define types, other procs should receive the correct definition from master. (with the current implementation inconsistencies might occur) */ /* test whether typ is valid */ if (typ>=nDescr) { DDD_PrintError('E', 2414, "invalid DDD_TYPE in DDD_TypeDefine"); HARD_EXIT; /*return;*/ } /* get object description */ desc = &(theTypeDefs[typ]); desc->currTypeDefCall++; if (desc->mode!=DDD_TYPE_DECLARED && desc->mode!=DDD_TYPE_CONTDEF) { if (desc->mode==DDD_TYPE_DEFINED) { DDD_PrintError('E', 2415, RegisterError(desc, 0, "DDD_TYPE already defined")); } else { DDD_PrintError('E', 2416, RegisterError(desc, 0, "undeclared DDD_TYPE")); } HARD_EXIT; /*return;*/ } /* initialize TYPE_DESC struct, only on first call */ if (desc->currTypeDefCall==1) ConstructDesc(desc); if (typ==0) /* i.e. typ==EL_DDDHDR */ { /* DDD_HDR also contains a DDD_HDR (sic!) */ desc->hasHeader = TRUE; } # ifdef DebugTypeDefine sprintf(cBuffer," DDD_TypeDefine(%s/%d)\n", desc->name, desc->currTypeDefCall); DDD_PrintDebug(cBuffer); # endif /* start variable arguments after "typ"-parameter */ va_start(ap, typ); #ifdef C_FRONTEND adr = va_arg(ap, char *); argno = 2; #endif #ifdef CPP_FRONTEND if (CPP_STRUCT(desc)) { adr = va_arg(ap, char *); argno = 2; } else {
DDD_RET DDD_JoinEnd (void) #endif { JIJoinPtrArray *arrayJIJoin = NULL; JIAddCplPtrArray *arrayJIAddCpl2 = NULL; JIAddCplPtrArray *arrayJIAddCpl3 = NULL; int obsolete, nRecvMsgs1, nRecvMsgs2, nRecvMsgs3, nSendMsgs; JOINMSG1 *sendMsgs1=NULL, *sm1=NULL; JOINMSG2 *sendMsgs2=NULL, *sm2=NULL; JOINMSG3 *sendMsgs3=NULL, *sm3=NULL; LC_MSGHANDLE *recvMsgs1=NULL, *recvMsgs2=NULL, *recvMsgs3=NULL; DDD_HDR *localCplObjs=NULL; size_t sendMem=0, recvMem=0; JIPartner *joinObjs = NULL; int nJoinObjs; #ifdef JoinMemFromHeap MarkHeap(); LC_SetMemMgr(memmgr_AllocTMEM, memmgr_FreeTMEM, memmgr_AllocHMEM, NULL); #endif STAT_SET_MODULE(DDD_MODULE_JOIN); STAT_ZEROALL; /* step mode and check whether call to JoinEnd is valid */ if (!JoinStepMode(JMODE_CMDS)) { DDD_PrintError('E', 7011, "DDD_JoinEnd() aborted"); HARD_EXIT; } /* PREPARATION PHASE */ /* get sorted array of JIJoin-items */ arrayJIJoin = JIJoinSet_GetArray(joinGlobals.setJIJoin); obsolete = JIJoinSet_GetNDiscarded(joinGlobals.setJIJoin); /* COMMUNICATION PHASE 1 all processors, where JoinObj-commands have been issued, send information about these commands to the target processors together with the GID of the objects on the target procs and the local priority. */ STAT_RESET; /* prepare msgs for JIJoin-items */ nSendMsgs = PreparePhase1Msgs(arrayJIJoin, &sendMsgs1, &sendMem); /* DisplayMemResources(); */ /* init communication topology */ nRecvMsgs1 = LC_Connect(joinGlobals.phase1msg_t); STAT_TIMER(T_JOIN_PREP_MSGS); STAT_RESET; /* build phase1 msgs on sender side and start send */ PackPhase1Msgs(sendMsgs1); STAT_TIMER(T_JOIN_PACK_SEND); /* now messages are in the net, use spare time */ STAT_RESET; /* get sorted list of local objects with couplings */ localCplObjs = LocalCoupledObjectsList(); if (localCplObjs==NULL && ddd_nCpls>0) { DDD_PrintError('E', 7020, "Cannot get list of coupled objects in DDD_JoinEnd(). Aborted"); HARD_EXIT; } if (obsolete>0) { if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_OBSOLETE) { int all = JIJoinSet_GetNItems(joinGlobals.setJIJoin); sprintf(cBuffer, "DDD MESG [%03d]: %4d from %4d join-cmds obsolete.\n", me, obsolete, all); DDD_PrintLine(cBuffer); } } STAT_TIMER(T_JOIN); /* nothing more to do until incoming messages arrive */ /* display information about send-messages on lowcomm-level */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MSGSALL) { DDD_SyncAll(); if (me==master) DDD_PrintLine("DDD JOIN_SHOW_MSGSALL: Phase1Msg.Send\n"); LC_PrintSendMsgs(); } /* wait for communication-completion (send AND receive) */ STAT_RESET; recvMsgs1 = LC_Communicate(); STAT_TIMER(T_JOIN_WAIT_RECV); /* display information about message buffer sizes */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MEMUSAGE) { int k; /* sum up sizes of receive mesg buffers */ for(k=0; k<nRecvMsgs1; k++) { recvMem += LC_GetBufferSize(recvMsgs1[k]); } sprintf(cBuffer, "DDD MESG [%03d]: SHOW_MEM " "msgs send=%010ld recv=%010ld all=%010ld\n", me, (long)sendMem, (long)recvMem, (long)(sendMem+recvMem)); DDD_PrintLine(cBuffer); } /* display information about recv-messages on lowcomm-level */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MSGSALL) { DDD_SyncAll(); if (me==master) DDD_PrintLine("DDD JOIN_SHOW_MSGSALL: Phase1Msg.Recv\n"); LC_PrintRecvMsgs(); } /* unpack messages */ STAT_RESET; UnpackPhase1Msgs(recvMsgs1, nRecvMsgs1, localCplObjs, NCpl_Get, &joinObjs, &nJoinObjs); LC_Cleanup(); STAT_TIMER(T_JOIN_UNPACK); /* COMMUNICATION PHASE 2 all processors which received notification of JoinObj-commands during phase 1 send AddCpl-requests to all copies of DDD objects, for which Joins had been issued remotely. */ /* get sorted array of JIAddCpl-items */ arrayJIAddCpl2 = JIAddCplSet_GetArray(joinGlobals.setJIAddCpl2); STAT_RESET; /* prepare msgs for JIAddCpl-items */ nSendMsgs = PreparePhase2Msgs(arrayJIAddCpl2, &sendMsgs2, &sendMem); /* DisplayMemResources(); */ /* init communication topology */ nRecvMsgs2 = LC_Connect(joinGlobals.phase2msg_t); STAT_TIMER(T_JOIN_PREP_MSGS); STAT_RESET; /* build phase2 msgs on sender side and start send */ PackPhase2Msgs(sendMsgs2); STAT_TIMER(T_JOIN_PACK_SEND); /* now messages are in the net, use spare time */ /* reorder Join-commands according to new_gid */ /* this ordering is needed in UnpackPhase3 */ if (JIJoinPtrArray_GetSize(arrayJIJoin) > 1) { qsort( JIJoinPtrArray_GetData(arrayJIJoin), JIJoinPtrArray_GetSize(arrayJIJoin), sizeof(JIJoin *), sort_NewGid); } /* nothing more to do until incoming messages arrive */ /* display information about send-messages on lowcomm-level */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MSGSALL) { DDD_SyncAll(); if (me==master) DDD_PrintLine("DDD JOIN_SHOW_MSGSALL: Phase2Msg.Send\n"); LC_PrintSendMsgs(); } /* wait for communication-completion (send AND receive) */ STAT_RESET; recvMsgs2 = LC_Communicate(); STAT_TIMER(T_JOIN_WAIT_RECV); /* display information about message buffer sizes */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MEMUSAGE) { int k; /* sum up sizes of receive mesg buffers */ for(k=0; k<nRecvMsgs2; k++) { recvMem += LC_GetBufferSize(recvMsgs2[k]); } sprintf(cBuffer, "DDD MESG [%03d]: SHOW_MEM " "msgs send=%010ld recv=%010ld all=%010ld\n", me, (long)sendMem, (long)recvMem, (long)(sendMem+recvMem)); DDD_PrintLine(cBuffer); } /* display information about recv-messages on lowcomm-level */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MSGSALL) { DDD_SyncAll(); if (me==master) DDD_PrintLine("DDD JOIN_SHOW_MSGSALL: Phase2Msg.Recv\n"); LC_PrintRecvMsgs(); } /* unpack messages */ STAT_RESET; UnpackPhase2Msgs(recvMsgs2, nRecvMsgs2, joinObjs, nJoinObjs, localCplObjs, NCpl_Get); LC_Cleanup(); STAT_TIMER(T_JOIN_UNPACK); for(; sendMsgs2!=NULL; sendMsgs2=sm2) { sm2 = sendMsgs2->next; FreeTmp(sendMsgs2, 0); } /* COMMUNICATION PHASE 3 all processors which received notification of JoinObj-commands during phase 1 send AddCpl-requests to the procs where the JoinObj-commands have been issued. One AddCpl-request is sent for each cpl in the local objects coupling list. One AddCpl-request is sent for each AddCpl-request received during phase 2. (i.e., two kinds of AddCpl-requests are send to the processors on which the JoinObj-commands have been issued. */ /* get sorted array of JIAddCpl-items */ arrayJIAddCpl3 = JIAddCplSet_GetArray(joinGlobals.setJIAddCpl3); STAT_RESET; /* prepare msgs for JIAddCpl-items */ nSendMsgs = PreparePhase3Msgs(arrayJIAddCpl3, &sendMsgs3, &sendMem); /* DisplayMemResources(); */ /* init communication topology */ nRecvMsgs3 = LC_Connect(joinGlobals.phase3msg_t); STAT_TIMER(T_JOIN_PREP_MSGS); STAT_RESET; /* build phase3 msgs on sender side and start send */ PackPhase3Msgs(sendMsgs3); STAT_TIMER(T_JOIN_PACK_SEND); /* now messages are in the net, use spare time */ /* ... */ /* nothing more to do until incoming messages arrive */ /* display information about send-messages on lowcomm-level */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MSGSALL) { DDD_SyncAll(); if (me==master) DDD_PrintLine("DDD JOIN_SHOW_MSGSALL: Phase3Msg.Send\n"); LC_PrintSendMsgs(); } /* wait for communication-completion (send AND receive) */ STAT_RESET; recvMsgs3 = LC_Communicate(); STAT_TIMER(T_JOIN_WAIT_RECV); /* display information about message buffer sizes */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MEMUSAGE) { int k; /* sum up sizes of receive mesg buffers */ for(k=0; k<nRecvMsgs3; k++) { recvMem += LC_GetBufferSize(recvMsgs3[k]); } sprintf(cBuffer, "DDD MESG [%03d]: SHOW_MEM " "msgs send=%010ld recv=%010ld all=%010ld\n", me, (long)sendMem, (long)recvMem, (long)(sendMem+recvMem)); DDD_PrintLine(cBuffer); } /* display information about recv-messages on lowcomm-level */ if (DDD_GetOption(OPT_INFO_JOIN) & JOIN_SHOW_MSGSALL) { DDD_SyncAll(); if (me==master) DDD_PrintLine("DDD JOIN_SHOW_MSGSALL: Phase3Msg.Recv\n"); LC_PrintRecvMsgs(); } /* unpack messages */ STAT_RESET; UnpackPhase3Msgs(recvMsgs3, nRecvMsgs3, arrayJIJoin); LC_Cleanup(); STAT_TIMER(T_JOIN_UNPACK); for(; sendMsgs3!=NULL; sendMsgs3=sm3) { sm3 = sendMsgs3->next; FreeTmp(sendMsgs3, 0); } /* free temporary storage */ JIJoinPtrArray_Free(arrayJIJoin); JIJoinSet_Reset(joinGlobals.setJIJoin); JIAddCplPtrArray_Free(arrayJIAddCpl2); JIAddCplSet_Reset(joinGlobals.setJIAddCpl2); JIAddCplPtrArray_Free(arrayJIAddCpl3); JIAddCplSet_Reset(joinGlobals.setJIAddCpl3); if (localCplObjs!=NULL) FreeTmp(localCplObjs, 0); if (joinObjs!=NULL) FreeTmp(joinObjs, 0); for(; sendMsgs1!=NULL; sendMsgs1=sm1) { sm1 = sendMsgs1->next; FreeTmp(sendMsgs1, 0); } #ifdef JoinMemFromHeap ReleaseHeap(); LC_SetMemMgr(memmgr_AllocTMEM, memmgr_FreeTMEM, memmgr_AllocTMEM, memmgr_FreeTMEM); #endif # if DebugJoin<=4 sprintf(cBuffer,"%4d: JoinEnd, before IFAllFromScratch().\n", me); DDD_PrintDebug(cBuffer); # endif /* re-create all interfaces and step JMODE */ STAT_RESET; IFAllFromScratch(); STAT_TIMER(T_JOIN_BUILD_IF); JoinStepMode(JMODE_BUSY); return(DDD_RET_OK); }