libcm is a C development framework with an emphasis on audio signal processing applications.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cmVirtNet.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. //| Copyright: (C) 2009-2020 Kevin Larke <contact AT larke DOT org>
  2. //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
  3. #include "cmGlobal.h"
  4. #include "cmRpt.h"
  5. #include "cmErr.h"
  6. #include "cmCtx.h"
  7. #include "cmMem.h"
  8. #include "cmMallocDebug.h"
  9. #include "cmLinkedHeap.h"
  10. #include "cmJson.h"
  11. #include "cmThread.h"
  12. #include "cmUdpPort.h"
  13. #include "cmUdpNet.h"
  14. #include "cmVirtNet.h"
  15. #include "cmText.h" // use by cmVnTest()
  16. enum { kOwnerVnFl = 0x01, kLocalVnFl = 0x02 };
  17. struct cmVn_str;
  18. typedef struct cmVnNode_str
  19. {
  20. cmChar_t* label;
  21. unsigned id;
  22. unsigned flags;
  23. cmVnH_t vnH; // handle of a local virtual net that is owned by this node
  24. cmTsMp1cH_t qH; // MPSC non-blocking queue to hold incoming msg's from local virt. net's
  25. struct cmVn_str* p; // pointer back to the cmVn_t which owns this NULL
  26. struct cmVnNode_str* link;
  27. } cmVnNode_t;
  28. typedef struct cmVn_str
  29. {
  30. cmErr_t err;
  31. cmUdpNetH_t udpH;
  32. cmVnNode_t* nodes;
  33. cmVnCb_t cbFunc;
  34. void* cbArg;
  35. unsigned ownerNodeId;
  36. } cmVn_t;
  37. cmVnH_t cmVnNullHandle = cmSTATIC_NULL_HANDLE;
  38. cmVn_t* _cmVnHandleToPtr( cmVnH_t h )
  39. {
  40. cmVn_t* p = (cmVn_t*)h.h;
  41. assert(p != NULL);
  42. return p;
  43. }
  44. void _cmVnNodeFree( cmVnNode_t* np )
  45. {
  46. if( np == NULL )
  47. return;
  48. if( cmTsMp1cIsValid(np->qH) )
  49. cmTsMp1cDestroy(&np->qH);
  50. np->vnH = cmVnNullHandle;
  51. cmMemFree(np->label);
  52. cmMemFree(np);
  53. }
  54. cmVnRC_t _cmVnDestroy( cmVn_t* p )
  55. {
  56. cmVnRC_t rc = kOkVnRC;
  57. if( cmUdpNetFree(&p->udpH) != kOkUnRC )
  58. return cmErrMsg(&p->err,kUdpNetFailVnRC,"The UDP network release failed.");
  59. cmVnNode_t* np = p->nodes;
  60. while( np != NULL )
  61. {
  62. cmVnNode_t* nnp = np->link;
  63. _cmVnNodeFree(np);
  64. np = nnp;
  65. }
  66. cmMemFree(p);
  67. return rc;
  68. }
  69. // This function is called by cmUdpNetReceive() which is called by cmVnReceive().
  70. void _cmVnUdpNetCb( void* cbArg, cmUdpNetH_t h, const char* data, unsigned dataByteCnt, unsigned remoteNodeId )
  71. {
  72. cmVnNode_t* np = (cmVnNode_t*)cbArg;
  73. assert( np->id == remoteNodeId );
  74. np->p->cbFunc(np->p->cbArg,np->id,dataByteCnt,data);
  75. }
  76. cmVnRC_t _cmVnSend( cmVn_t* p, cmVnNode_t* np, unsigned byteCnt, const cmChar_t* buf )
  77. {
  78. cmVnRC_t rc = kOkVnRC;
  79. // if the node is local then send the msg directly
  80. if( cmIsFlag(np->flags,kLocalVnFl) )
  81. {
  82. if( cmVnIsValid(np->vnH) )
  83. rc = cmVnRecvFromLocal(np->vnH,p->ownerNodeId,byteCnt,buf);
  84. }
  85. else
  86. {
  87. // if the node is remote then send the msg via UDP
  88. if( cmUdpNetSendById(p->udpH,np->id,buf,byteCnt) != kOkUnRC )
  89. rc = cmErrMsg( &p->err, kUdpNetFailVnRC, "UDP Net send to remote node '%s' failed.",cmStringNullGuard(np->label));
  90. }
  91. return rc;
  92. }
  93. cmVnNode_t* _cmVnNodeFindById( cmVn_t* p, unsigned id )
  94. {
  95. cmVnNode_t* np = p->nodes;
  96. for(; np!=NULL; np=np->link)
  97. if( np->id == id )
  98. return np;
  99. return NULL;
  100. }
  101. cmVnNode_t* _cmVnNodeFindByLabel( cmVn_t* p, const cmChar_t* label )
  102. {
  103. cmVnNode_t* np = p->nodes;
  104. for(; np!=NULL; np=np->link)
  105. if( strcmp(np->label,label)==0 )
  106. return np;
  107. return NULL;
  108. }
  109. cmVnNode_t* _cmVnNodeFindOwner( cmVn_t* p )
  110. {
  111. cmVnNode_t* np = p->nodes;
  112. for(; np!=NULL; np=np->link)
  113. if( cmIsFlag(np->flags,kOwnerVnFl) )
  114. return np;
  115. return NULL;
  116. }
  117. cmVnRC_t _cmVnCreateNode( cmVn_t* p, const cmChar_t* label, unsigned nodeId, unsigned flags, cmVnNode_t** npp )
  118. {
  119. cmVnNode_t* np;
  120. if((np = _cmVnNodeFindById(p,nodeId)) != NULL )
  121. return cmErrMsg(&p->err,kDuplicateNodeIdVnRC,"The node id=%i is already in use for '%s'.",cmStringNullGuard(np->label));
  122. if((np = _cmVnNodeFindByLabel(p,label)) != NULL )
  123. return cmErrMsg(&p->err,kDuplicateNodeLabelVnRC,"The node label '%s' is already used by another node.",cmStringNullGuard(label));
  124. np = cmMemAllocZ(cmVnNode_t,1);
  125. np->label = cmMemAllocStr(label);
  126. np->id = nodeId;
  127. np->flags = flags;
  128. np->p = p;
  129. *npp = np;
  130. return kOkVnRC;
  131. }
  132. void _cmVnNodeLink( cmVn_t* p, cmVnNode_t* np )
  133. {
  134. cmVnNode_t* cnp = p->nodes;
  135. if( cnp == NULL )
  136. p->nodes = np;
  137. else
  138. {
  139. while( cnp->link != NULL )
  140. cnp = cnp->link;
  141. cnp->link = np;
  142. }
  143. }
  144. cmVnRC_t _cmVnCreateOwnerNode( cmVn_t* p, const cmChar_t* label, unsigned nodeId, unsigned udpRecvBufByteCnt, unsigned udpRecvTimeOutMs, const cmChar_t* ipAddr, unsigned ipPort )
  145. {
  146. cmVnRC_t rc = kOkVnRC;
  147. cmVnNode_t* np;
  148. // create a generic node
  149. if((rc = _cmVnCreateNode(p, label,nodeId, kOwnerVnFl, &np )) != kOkVnRC )
  150. goto errLabel;
  151. // initialize the UDP net with the owner node
  152. if( cmUdpNetInit(p->udpH,label,nodeId,ipPort,_cmVnUdpNetCb,np,udpRecvBufByteCnt,udpRecvTimeOutMs) != kOkUnRC )
  153. {
  154. rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"UDP network initialization failed for node:'%s'.",cmStringNullGuard(label));
  155. goto errLabel;
  156. }
  157. _cmVnNodeLink(p,np);
  158. p->ownerNodeId = nodeId;
  159. errLabel:
  160. if( rc != kOkVnRC )
  161. _cmVnNodeFree(np);
  162. return rc;
  163. }
  164. //------------------------------------------------------------------------------------------------------------
  165. cmVnRC_t cmVnCreate( cmCtx_t* ctx, cmVnH_t* hp, cmVnCb_t cbFunc, void* cbArg, const cmChar_t* label, unsigned nodeId, unsigned udpRecvBufByteCnt, unsigned udpRecvTimeOutMs, const cmChar_t* ipAddr, unsigned ipPort )
  166. {
  167. cmVnRC_t rc;
  168. if((rc = cmVnDestroy(hp)) != kOkVnRC )
  169. return rc;
  170. cmVn_t* p = cmMemAllocZ(cmVn_t,1);
  171. cmErrSetup(&p->err,&ctx->rpt,"cmVirtNet");
  172. if( cmUdpNetAlloc(ctx,&p->udpH) != kOkUnRC )
  173. {
  174. rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"The UDP network allocation failed.");
  175. goto errLabel;
  176. }
  177. // create the owner node
  178. if((rc = _cmVnCreateOwnerNode(p,label,nodeId, udpRecvBufByteCnt, udpRecvTimeOutMs, ipAddr, ipPort )) != kOkVnRC )
  179. goto errLabel;
  180. p->ownerNodeId = nodeId;
  181. p->cbFunc = cbFunc;
  182. p->cbArg = cbArg;
  183. hp->h = p;
  184. errLabel:
  185. if( rc != kOkUdpRC )
  186. _cmVnDestroy(p);
  187. return rc;
  188. }
  189. cmVnRC_t cmVnDestroy( cmVnH_t* hp )
  190. {
  191. cmVnRC_t rc = kOkVnRC;
  192. if( hp == NULL || cmVnIsValid(*hp)==false )
  193. return rc;
  194. cmVn_t* p = _cmVnHandleToPtr(*hp);
  195. if((rc = _cmVnDestroy(p)) != kOkVnRC )
  196. return rc;
  197. hp->h = NULL;
  198. return rc;
  199. }
  200. bool cmVnIsValid( cmVnH_t h )
  201. { return h.h != NULL; }
  202. cmVnRC_t cmVnEnableListen( cmVnH_t h, bool enableFl )
  203. {
  204. cmVn_t* p = _cmVnHandleToPtr(h);
  205. if( cmUdpNetEnableListen( p->udpH, enableFl ) != kOkUnRC )
  206. return cmErrMsg(&p->err,kUdpNetFailVnRC,"UDP listen enable failed.");
  207. return kOkVnRC;
  208. }
  209. // This function is called by cmTsMp1cDequeueMsg() which is called by cmVnReceive().
  210. cmRC_t _cmVnTsQueueCb(void* userCbPtr, unsigned msgByteCnt, const void* msgDataPtr )
  211. {
  212. cmVnNode_t* np = (cmVnNode_t*)userCbPtr;
  213. return np->p->cbFunc(np->p->cbArg,np->id,msgByteCnt,msgDataPtr);
  214. }
  215. cmVnRC_t cmVnCreateLocalNode( cmVnH_t h, const cmChar_t* label, unsigned nodeId, cmVnH_t vnH, unsigned queBufByteCnt )
  216. {
  217. cmVnRC_t rc = kOkVnRC;
  218. cmVn_t* p = _cmVnHandleToPtr(h);
  219. cmVnNode_t* np = NULL;
  220. // create a generic node
  221. if((rc = _cmVnCreateNode(p,label,nodeId,kLocalVnFl,&np )) != kOkVnRC )
  222. goto errLabel;
  223. if( cmTsMp1cCreate( &np->qH, queBufByteCnt, _cmVnTsQueueCb, np, p->err.rpt ) != kOkThRC )
  224. {
  225. rc = cmErrMsg(&p->err,kQueueFailVnRC,"The internal thread-safe queue creation failed for local node '%s'.",cmStringNullGuard(label));
  226. goto errLabel;
  227. }
  228. np->vnH = vnH;
  229. _cmVnNodeLink(p,np);
  230. errLabel:
  231. if( rc != kOkVnRC )
  232. _cmVnNodeFree(np);
  233. return rc;
  234. }
  235. cmVnRC_t cmVnCreateRemoteNode( cmVnH_t h, const cmChar_t* label, unsigned nodeId, const cmChar_t* ipAddr, unsigned ipPort )
  236. {
  237. cmVnRC_t rc = kOkVnRC;
  238. cmVn_t* p = _cmVnHandleToPtr(h);
  239. cmVnNode_t* np;
  240. // creaet a generic node
  241. if((rc = _cmVnCreateNode(p, label,nodeId, 0, &np )) != kOkVnRC )
  242. goto errLabel;
  243. if( ipAddr!=NULL )
  244. {
  245. if( cmUdpNetRegisterRemote(p->udpH,label,nodeId,ipAddr,ipPort) != kOkUnRC )
  246. {
  247. rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"UDP network remote registration failed for node:'%s'.",cmStringNullGuard(label));
  248. goto errLabel;
  249. }
  250. }
  251. _cmVnNodeLink(p,np);
  252. errLabel:
  253. if( rc != kOkVnRC )
  254. _cmVnNodeFree(np);
  255. return rc;
  256. }
  257. cmVnRC_t cmVnRecvFromLocal( cmVnH_t h, unsigned srcNodeId, unsigned byteCnt, const cmChar_t* buf )
  258. {
  259. cmVnRC_t rc = kOkVnRC;
  260. cmVnNode_t* np;
  261. cmVn_t* p = _cmVnHandleToPtr(h);
  262. if(( np = _cmVnNodeFindById(p,srcNodeId)) == NULL )
  263. return cmErrMsg(&p->err,kNodeNotFoundVnRC,"The node with id=%i could not be found.",srcNodeId);
  264. if( cmTsMp1cIsValid(np->qH) == false )
  265. return cmErrMsg(&p->err,kQueueFailVnRC,"The internal MPSC queue for the node '%s' is not valid. Is this a local node?",cmStringNullGuard(np->label));
  266. if( cmTsMp1cEnqueueMsg(np->qH,buf,byteCnt) != kOkThRC )
  267. return cmErrMsg(&p->err,kQueueFailVnRC,"Enqueue failed on the internal MPSC queue for the node '%s'.",cmStringNullGuard(np->label));
  268. return rc;
  269. }
  270. cmVnRC_t cmVnSendById( cmVnH_t h, unsigned nodeId, unsigned byteCnt, const cmChar_t* buf )
  271. {
  272. cmVnNode_t* np;
  273. cmVn_t* p = _cmVnHandleToPtr(h);
  274. if(( np = _cmVnNodeFindById(p,nodeId)) == NULL )
  275. return cmErrMsg(&p->err,kNodeNotFoundVnRC,"The node with id=%i could not be found.",nodeId);
  276. return _cmVnSend(p,np,byteCnt,buf);
  277. }
  278. cmVnRC_t cmVnSendByLabel( cmVnH_t h, const cmChar_t* nodeLabel, unsigned byteCnt, const cmChar_t* buf )
  279. {
  280. cmVnNode_t* np;
  281. cmVn_t* p = _cmVnHandleToPtr(h);
  282. if(( np = _cmVnNodeFindByLabel(p,nodeLabel)) == NULL )
  283. return cmErrMsg(&p->err,kNodeNotFoundVnRC,"The node named '%s' could not be found.",cmStringNullGuard(nodeLabel));
  284. return _cmVnSend(p,np,byteCnt,buf);
  285. }
  286. cmVnRC_t _cmVnRecvQueueMsgs( cmVn_t* p, cmVnNode_t* np, unsigned *msgCntPtr )
  287. {
  288. unsigned i;
  289. unsigned mn = *msgCntPtr;
  290. *msgCntPtr = 0;
  291. for(i=0; (i<mn || mn==cmInvalidCnt) && cmTsMp1cMsgWaiting(np->qH); ++i)
  292. {
  293. // calling this function results in calls to _cmVnTsQueueCb()
  294. if( cmTsMp1cDequeueMsg( np->qH,NULL,0) != kOkThRC )
  295. return cmErrMsg(&p->err,kQueueFailVnRC,"Msg deque failed for node '%s'.",cmStringNullGuard(np->label));
  296. }
  297. *msgCntPtr = i;
  298. return kOkVnRC;
  299. }
  300. cmVnRC_t cmVnReceive( cmVnH_t h, unsigned* msgCntPtr )
  301. {
  302. cmVnRC_t rc = kOkVnRC;
  303. cmVn_t* p = _cmVnHandleToPtr(h);
  304. cmVnNode_t* np = p->nodes;
  305. unsigned mn = msgCntPtr == NULL ? cmInvalidCnt : *msgCntPtr;
  306. if( msgCntPtr != NULL )
  307. *msgCntPtr = 0;
  308. for(; np!=NULL && rc==kOkVnRC; np=np->link)
  309. {
  310. unsigned msgCnt = mn;
  311. switch( np->flags & (kOwnerVnFl | kLocalVnFl) )
  312. {
  313. case kOwnerVnFl:
  314. break;
  315. case kLocalVnFl:
  316. rc = _cmVnRecvQueueMsgs(p,np,&msgCnt);
  317. break;
  318. default:
  319. if( cmUdpNetReceive(p->udpH,msgCntPtr==NULL?NULL:&msgCnt) != kOkUnRC )
  320. rc = cmErrMsg(&p->err,kUdpNetFailVnRC,"The UDP net receive failed on node '%s'.",cmStringNullGuard(np->label));
  321. break;
  322. }
  323. if( rc == kOkVnRC && msgCntPtr != NULL )
  324. *msgCntPtr += msgCnt;
  325. }
  326. return rc;
  327. }
  328. //---------------------------------------------------------------------------------------------------
  329. unsigned udpRecvBufByteCnt = 8192;
  330. unsigned udpRecvTimeOutMs = 100;
  331. unsigned queueBufByteCnt = 8192;
  332. void* cbArg = NULL;
  333. typedef struct
  334. {
  335. const cmChar_t* label;
  336. unsigned id;
  337. const cmChar_t* ipAddr;
  338. unsigned ipPort;
  339. cmVnH_t vnH;
  340. } node_t;
  341. cmVnRC_t _cmVnTestCb( void* cbArg, unsigned srcNodeId, unsigned byteCnt, const char* buf )
  342. {
  343. printf("src node:%i bytes:%i %s\n",srcNodeId,byteCnt,buf);
  344. return kOkVnRC;
  345. }
  346. cmVnRC_t _cmVnTestCreateLocalNet( cmCtx_t* ctx, cmErr_t* err, unsigned id, node_t* nodeArray )
  347. {
  348. cmVnRC_t rc = kOkVnRC;
  349. if((rc = cmVnCreate(ctx, &nodeArray[id].vnH, _cmVnTestCb, cbArg, nodeArray[id].label, nodeArray[id].id, udpRecvBufByteCnt, udpRecvTimeOutMs, nodeArray[id].ipAddr, nodeArray[id].ipPort )) != kOkVnRC )
  350. rc = cmErrMsg(err,rc,"Virtual network create failed.");
  351. return rc;
  352. }
  353. cmVnRC_t _cmVnTestCreateVirtNodes( cmErr_t* err, unsigned id, node_t* nodeArray )
  354. {
  355. unsigned rc = kOkVnRC;
  356. unsigned i;
  357. for(i=0; nodeArray[i].label != NULL; ++i)
  358. {
  359. // if this is a local node
  360. if( strcmp(nodeArray[i].ipAddr,nodeArray[id].ipAddr) == 0 )
  361. {
  362. // if this is not the owner node
  363. if( i != id )
  364. if((rc = cmVnCreateLocalNode( nodeArray[id].vnH, nodeArray[i].label, nodeArray[i].id, nodeArray[i].vnH, queueBufByteCnt )) != kOkVnRC )
  365. {
  366. cmErrMsg(err,rc,"Local node create failed for node:'%s'.",nodeArray[i].label);
  367. goto errLabel;
  368. }
  369. }
  370. else // this must be a remote node
  371. {
  372. if((rc = cmVnCreateRemoteNode(nodeArray[id].vnH, nodeArray[i].label, nodeArray[i].id, nodeArray[i].ipAddr, nodeArray[i].ipPort )) != kOkVnRC )
  373. {
  374. cmErrMsg(err,rc,"Remote node create failed for node '%s'.",nodeArray[i].label);
  375. goto errLabel;
  376. }
  377. }
  378. }
  379. errLabel:
  380. return rc;
  381. }
  382. cmVnRC_t _cmVnTestSend( cmErr_t* err, node_t* nodeArray, unsigned id, unsigned i, unsigned n )
  383. {
  384. cmVnRC_t rc = kOkVnRC;
  385. if( id == i )
  386. {
  387. printf("A node cannot send to itself.\n");
  388. return rc;
  389. }
  390. const cmChar_t* buf = cmTsPrintfS("msg-%i",n);
  391. unsigned bufByteCnt = strlen(buf)+1;
  392. printf("Sending from '%s' to '%s'.\n", cmStringNullGuard(nodeArray[id].label), cmStringNullGuard(nodeArray[i].label));
  393. if((rc = cmVnSendById(nodeArray[id].vnH, nodeArray[i].id, bufByteCnt, buf ))!= kOkVnRC )
  394. cmErrMsg(err,rc,"Send from '%s' to '%s' failed.", cmStringNullGuard(nodeArray[id].label), cmStringNullGuard(nodeArray[i].label));
  395. return rc;
  396. }
  397. cmVnRC_t cmVnTest( cmCtx_t* ctx )
  398. {
  399. cmVnRC_t rc = kOkVnRC;
  400. cmErr_t err;
  401. unsigned id = 0;
  402. unsigned i;
  403. unsigned n = 0;
  404. node_t nodeArray[] =
  405. {
  406. { "whirl-0", 10, "192.168.15.109", 5768, cmVnNullHandle },
  407. { "whirl-1", 20, "192.168.15.109", 5767, cmVnNullHandle },
  408. { "thunk-0", 30, "192.168.15.111", 5766, cmVnNullHandle },
  409. { NULL, -1, NULL }
  410. };
  411. cmErrSetup(&err,&ctx->rpt,"cmVnTest");
  412. // create the virt networks for the local nodes
  413. for(i=0; nodeArray[i].label !=NULL; ++i)
  414. if( strcmp(nodeArray[i].ipAddr,nodeArray[id].ipAddr ) == 0 )
  415. if((rc = _cmVnTestCreateLocalNet(ctx,&err,i,nodeArray)) != kOkVnRC )
  416. goto errLabel;
  417. // create the virtual nodes for each local virtual network
  418. for(i=0; nodeArray[i].label != NULL; ++i)
  419. if( cmVnIsValid(nodeArray[i].vnH) )
  420. if((rc = _cmVnTestCreateVirtNodes(&err, i, nodeArray )) != kOkVnRC )
  421. break;
  422. char c;
  423. while((c=getchar()) != 'q')
  424. {
  425. bool promptFl = true;
  426. switch( c )
  427. {
  428. case '0':
  429. case '1':
  430. case '2':
  431. _cmVnTestSend(&err, nodeArray, id, (unsigned)c - (unsigned)'0', n );
  432. ++n;
  433. break;
  434. }
  435. for(i=0; nodeArray[i].label!=NULL; ++i)
  436. if( cmVnIsValid(nodeArray[i].vnH) )
  437. cmVnReceive(nodeArray[i].vnH,NULL);
  438. if( promptFl )
  439. cmRptPrintf(&ctx->rpt,"%i> ",n);
  440. }
  441. errLabel:
  442. // destroy the virtual networks
  443. for(i=0; nodeArray[i].label!=NULL; ++i)
  444. if( cmVnIsValid(nodeArray[i].vnH) )
  445. if((rc = cmVnDestroy(&nodeArray[i].vnH)) != kOkVnRC )
  446. cmErrMsg(&err,rc,"Node destroy failed for node '%s'.",cmStringNullGuard(nodeArray[i].label));
  447. return rc;
  448. }