mpbase.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #define mp_decl DECL_EXPORT
  14. #include "platform.h"
  15. #include "jlib.hpp"
  16. #include "jlog.hpp"
  17. #include "jtime.hpp"
  18. #include "mpbase.hpp"
  19. static INode *MyNode=NULL;
  20. static INode *NullNode=NULL;
  21. class MPNode: implements INode, public CInterface
  22. {
  23. protected: friend class MPNodeCache;
  24. SocketEndpoint ep;
  25. public:
  26. IMPLEMENT_IINTERFACE;
  27. MPNode(const SocketEndpoint &_ep)
  28. : ep(_ep)
  29. {
  30. #ifdef _DEBUG
  31. // assertex(!_ep.LoopBack()); // localhost not supported for nodes
  32. #endif
  33. }
  34. bool equals(const INode *n) const { return endpoint().equals(n->endpoint()); }
  35. void serialize(MemoryBuffer &tgt)
  36. {
  37. ep.serialize(tgt);
  38. }
  39. static MPNode *deserialize(MemoryBuffer &src);
  40. unsigned getHash() const { return ep.hash(0); }
  41. virtual bool isHost() const
  42. {
  43. return ep.isHost();
  44. }
  45. virtual bool isLocalTo(INode *to) const
  46. {
  47. return ep.ipequals(to->endpoint());
  48. }
  49. const SocketEndpoint &endpoint() const { return ep; }
  50. };
  51. class MPNodeCache: public SuperHashTableOf<MPNode,SocketEndpoint>
  52. {
  53. CriticalSection sect;
  54. public:
  55. ~MPNodeCache()
  56. {
  57. _releaseAll();
  58. }
  59. void onAdd(void *)
  60. {
  61. // not used
  62. }
  63. void onRemove(void *e)
  64. {
  65. MPNode &elem=*(MPNode *)e;
  66. elem.Release();
  67. }
  68. unsigned getHashFromElement(const void *e) const
  69. {
  70. const MPNode &elem=*(const MPNode *)e;
  71. return elem.ep.hash(0);
  72. }
  73. unsigned getHashFromFindParam(const void *fp) const
  74. {
  75. return ((const SocketEndpoint *)fp)->hash(0);
  76. }
  77. const void * getFindParam(const void *p) const
  78. {
  79. const MPNode &elem=*(const MPNode *)p;
  80. return &elem.ep;
  81. }
  82. bool matchesFindParam(const void * et, const void *fp, unsigned) const
  83. {
  84. return ((MPNode *)et)->ep.equals(*(SocketEndpoint *)fp);
  85. }
  86. IMPLEMENT_SUPERHASHTABLEOF_REF_FIND(MPNode,SocketEndpoint);
  87. MPNode *lookup(const SocketEndpoint &ep)
  88. {
  89. CriticalBlock block(sect);
  90. MPNode *item=SuperHashTableOf<MPNode,SocketEndpoint>::find(&ep);
  91. if (!item) {
  92. item = new MPNode(ep);
  93. add(*item);
  94. }
  95. return LINK(item);
  96. }
  97. } *NodeCache = NULL;
  98. MPNode *MPNode::deserialize(MemoryBuffer &src)
  99. {
  100. SocketEndpoint ep;
  101. ep.deserialize(src);
  102. if (NodeCache)
  103. return NodeCache->lookup(ep);
  104. return new MPNode(ep);
  105. }
  106. class CGroup: implements IGroup, public CInterface
  107. {
  108. protected: friend class CNodeIterator;
  109. rank_t count;
  110. mutable rank_t myrank;
  111. INode **nodes;
  112. public:
  113. IMPLEMENT_IINTERFACE;
  114. CGroup(rank_t _count,INode **_nodes=NULL)
  115. {
  116. count = _count;
  117. myrank = RANK_NULL;
  118. nodes = count?(INode **)malloc(count*sizeof(INode *)):NULL;
  119. if (_nodes) {
  120. for (rank_t i=0; i<count; i++)
  121. nodes[i] = LINK(_nodes[i]);
  122. }
  123. }
  124. CGroup(rank_t _count,const SocketEndpoint *ep)
  125. {
  126. count = _count;
  127. myrank = RANK_NULL;
  128. nodes = count?(INode **)malloc(count*sizeof(INode *)):NULL;
  129. for (rank_t i=0; i<count; i++) {
  130. if (NodeCache)
  131. nodes[i] = NodeCache->lookup(*ep);
  132. else
  133. nodes[i] = new MPNode(*ep);
  134. ep++;
  135. }
  136. }
  137. CGroup(SocketEndpointArray &epa)
  138. {
  139. count = epa.ordinality();
  140. myrank = RANK_NULL;
  141. nodes = count?(INode **)malloc(count*sizeof(INode *)):NULL;
  142. for (rank_t i=0; i<count; i++) {
  143. if (NodeCache)
  144. nodes[i] = NodeCache->lookup(epa.item(i));
  145. else
  146. nodes[i] = new MPNode(epa.item(i));
  147. }
  148. }
  149. ~CGroup()
  150. {
  151. for (rank_t i=0; i<count; i++)
  152. nodes[i]->Release();
  153. free(nodes);
  154. }
  155. rank_t ordinality() const { return count; }
  156. rank_t rank(const SocketEndpoint &ep) const
  157. {
  158. rank_t i=count;
  159. while (i) {
  160. i--;
  161. // a group is a list of IpAddresses or a list of full endpoints
  162. SocketEndpoint nep=nodes[i]->endpoint();
  163. if (nep.port) {
  164. if (ep.equals(nep))
  165. return i;
  166. }
  167. else if (ep.ipequals(nep)) // ip list so just check IP
  168. return i;
  169. }
  170. return RANK_NULL;
  171. }
  172. rank_t rank(INode *node) const { return node?rank(node->endpoint()):RANK_NULL; }
  173. rank_t rank() const { if (myrank==RANK_NULL) myrank = rank(MyNode); return myrank; }
  174. GroupRelation compare(const IGroup *grp) const
  175. {
  176. if (grp) {
  177. rank_t r1=ordinality();
  178. rank_t r2=grp->ordinality();
  179. rank_t r;
  180. if (r1==0) {
  181. if (r2==0)
  182. return GRidentical;
  183. return GRdisjoint;
  184. }
  185. if (r2==0)
  186. return GRdisjoint;
  187. bool somematch=false;
  188. if (r1==r2) { // check for identical
  189. r=r1;
  190. for (;;) {
  191. r--;
  192. if (!nodes[r]->equals(&grp->queryNode(r)))
  193. break;
  194. somematch = true;
  195. if (r==0)
  196. return GRidentical;
  197. }
  198. }
  199. else if (r2>r1) {
  200. for (r=0;r<r1;r++)
  201. if (!nodes[r]->equals(&grp->queryNode(r)))
  202. break;
  203. if (r==r1)
  204. return GRbasesubset;
  205. }
  206. else {
  207. for (r=0;r<r1;r++)
  208. if (!nodes[r]->equals(&grp->queryNode(r%r2)))
  209. break;
  210. if (r==r1)
  211. return GRwrappedsuperset;
  212. }
  213. // the following could be improved
  214. bool *matched2=(bool *)calloc(r2,sizeof(bool));
  215. bool anymatched = false;
  216. bool allmatched1 = true;
  217. do {
  218. r1--;
  219. r=r2;
  220. for (;;) {
  221. r--;
  222. if (!matched2[r]) {
  223. if (nodes[r1]->equals(&grp->queryNode(r))) {
  224. matched2[r] = true;
  225. anymatched = true;
  226. break;
  227. }
  228. }
  229. if (r==0) {
  230. allmatched1 = false;
  231. break;
  232. }
  233. }
  234. } while (r1);
  235. bool allmatched2 = true;
  236. do {
  237. r2--;
  238. if (!matched2[r2])
  239. allmatched2 = false;
  240. } while (r2);
  241. free(matched2);
  242. if (allmatched1) {
  243. if (allmatched2)
  244. return GRdifferentorder;
  245. return GRsubset;
  246. }
  247. if (allmatched2)
  248. return GRsuperset;
  249. if (anymatched)
  250. return GRintersection;
  251. }
  252. return GRdisjoint;
  253. }
  254. bool equals(const IGroup *grp) const
  255. {
  256. if (!grp)
  257. return false;
  258. rank_t r1=ordinality();
  259. if (r1!=grp->ordinality())
  260. return false;
  261. for (rank_t r=0;r<r1;r++)
  262. if (!nodes[r]->equals(&grp->queryNode(r)))
  263. return false;
  264. return true;
  265. }
  266. void translate(const IGroup *othergroup, rank_t nranks, const rank_t *otherranks, rank_t *resranks ) const
  267. {
  268. while (nranks) {
  269. *resranks = rank(&othergroup->queryNode(*otherranks));
  270. nranks--;
  271. resranks++;
  272. otherranks++;
  273. }
  274. }
  275. IGroup *subset(rank_t start,rank_t num) const
  276. {
  277. CGroup *newgrp = new CGroup(num);
  278. while (num) {
  279. num--;
  280. newgrp->nodes[num] = LINK(nodes[start+num]);
  281. }
  282. return newgrp;
  283. }
  284. virtual IGroup *subset(const rank_t *order,rank_t num) const
  285. {
  286. CGroup *newgrp = new CGroup(num);
  287. for( rank_t i=0; i<num; i++ ) {
  288. newgrp->nodes[i] = LINK(nodes[*order]);
  289. order++;
  290. }
  291. return newgrp;
  292. }
  293. virtual IGroup *combine(const IGroup *grp) const
  294. {
  295. rank_t g2ord = grp->ordinality();
  296. rank_t j = 0;
  297. INode **tmp = (INode **)malloc(g2ord*sizeof(INode *));
  298. rank_t i;
  299. for (i=0; i<g2ord; i++) {
  300. if (rank(&grp->queryNode(i))==RANK_NULL) {
  301. tmp[j] = grp->getNode(i);
  302. j++;
  303. }
  304. }
  305. CGroup *newgrp = new CGroup(count+j);
  306. for (i=0; i<count; i++)
  307. newgrp->nodes[i] = LINK(nodes[i]);
  308. for (rank_t k=0;k<j; k++)
  309. newgrp->nodes[i++] = tmp[k];
  310. free(tmp);
  311. return newgrp;
  312. }
  313. bool isMember(INode *node) const
  314. {
  315. if (!node)
  316. return false;
  317. return rank(node->endpoint())!=RANK_NULL;
  318. }
  319. bool isMember() const
  320. {
  321. assertex(MyNode);
  322. return rank()!=RANK_NULL;
  323. }
  324. unsigned distance(const IpAddress &ip) const
  325. {
  326. unsigned bestdist = (unsigned)-1;
  327. for (unsigned i=0;i<count;i++) {
  328. unsigned d = ip.ipdistance(nodes[i]->endpoint());
  329. if (d<bestdist)
  330. bestdist = d;
  331. }
  332. return bestdist;
  333. }
  334. unsigned distance(const IGroup *grp) const
  335. {
  336. if (!grp)
  337. return (unsigned)-1;
  338. unsigned c2 = grp->ordinality();
  339. if (c2<count)
  340. return grp->distance(this);
  341. unsigned ret = c2;
  342. unsigned bestdist = (unsigned)-1;
  343. for (unsigned i=0;i<count;i++) {
  344. INode &node1 = *nodes[i];
  345. if (node1.equals(&grp->queryNode(i)))
  346. ret--;
  347. else {
  348. unsigned d = grp->distance(node1.endpoint());
  349. if (d<bestdist)
  350. bestdist = d;
  351. }
  352. }
  353. if (bestdist!=(unsigned)-1)
  354. ret += bestdist;
  355. return ret;
  356. }
  357. IGroup *diff(const IGroup *g) const
  358. {
  359. PointerArray toadd;
  360. ForEachNodeInGroup(i,*this) {
  361. INode *node = &queryNode(i);
  362. if (node&&!g->isMember(node))
  363. toadd.append(node);
  364. }
  365. ForEachNodeInGroup(j,*g) {
  366. INode *node = &g->queryNode(j);
  367. if (node&&!isMember(node))
  368. toadd.append(node);
  369. }
  370. unsigned num = toadd.ordinality();
  371. CGroup *newgrp = new CGroup(num);
  372. while (num) {
  373. num--;
  374. newgrp->nodes[num] = LINK(((INode *)toadd.item(num)));
  375. }
  376. return newgrp;
  377. }
  378. bool overlaps(const IGroup *grp) const
  379. {
  380. if (grp) {
  381. ForEachNodeInGroup(i,*grp) {
  382. if (isMember(&grp->queryNode(i)))
  383. return true;
  384. }
  385. }
  386. return false;
  387. }
  388. IGroup *intersect(const IGroup *g) const
  389. {
  390. PointerArray toadd;
  391. ForEachNodeInGroup(i,*this) {
  392. INode *node = &queryNode(i);
  393. if (node&&g->isMember(node))
  394. toadd.append(node);
  395. }
  396. unsigned num = toadd.ordinality();
  397. CGroup *newgrp = new CGroup(num);
  398. while (num) {
  399. num--;
  400. newgrp->nodes[num] = LINK(((INode *)toadd.item(num)));
  401. }
  402. return newgrp;
  403. }
  404. IGroup *swap(rank_t r1,rank_t r2) const
  405. {
  406. CGroup *newgrp = new CGroup(count);
  407. rank_t i;
  408. for(i=0; i<count; i++ ) {
  409. if ((i==r1)&&(r2<count))
  410. newgrp->nodes[i] = LINK(nodes[r2]);
  411. if ((i==r2)&&(r1<count))
  412. newgrp->nodes[i] = LINK(nodes[r1]);
  413. else
  414. newgrp->nodes[i] = LINK(nodes[i]);
  415. }
  416. return newgrp;
  417. }
  418. virtual IGroup *add(INode *node) const
  419. {
  420. CGroup *newgrp = new CGroup(count+1);
  421. rank_t i;
  422. for(i=0; i<count; i++ ) {
  423. newgrp->nodes[i] = LINK(nodes[i]);
  424. }
  425. newgrp->nodes[i] = LINK(node);
  426. return newgrp;
  427. }
  428. virtual IGroup *add(INode *node,unsigned pos) const
  429. {
  430. CGroup *newgrp = new CGroup(count+1);
  431. unsigned j = 0;
  432. for( rank_t i=0; i<count; i++ ) {
  433. if (j==pos) {
  434. newgrp->nodes[j++] = LINK(node);
  435. }
  436. newgrp->nodes[j++] = LINK(nodes[i]);
  437. }
  438. return newgrp;
  439. }
  440. virtual IGroup *remove(unsigned pos) const
  441. {
  442. assertex(pos<count);
  443. CGroup *newgrp = new CGroup(count-1);
  444. unsigned j=0;
  445. for( rank_t i=0; i<count; i++ ) {
  446. if (i!=pos)
  447. newgrp->nodes[j++] = LINK(nodes[i]);
  448. }
  449. return newgrp;
  450. }
  451. virtual IGroup *rotate(int num) const
  452. {
  453. CGroup *newgrp = new CGroup(count);
  454. bool neg = false;
  455. if (num<0) {
  456. num=-num;
  457. neg = true;
  458. }
  459. unsigned j=(num%count);
  460. for( rank_t i=0; i<count; i++ ) {
  461. if (neg)
  462. newgrp->nodes[i] = LINK(nodes[j]);
  463. else
  464. newgrp->nodes[j] = LINK(nodes[i]);
  465. j++;
  466. if (j==count)
  467. j = 0;
  468. }
  469. return newgrp;
  470. }
  471. INodeIterator *getIterator(rank_t start=0,rank_t num=RANK_NULL) const ;
  472. INode &queryNode(rank_t r) const
  473. {
  474. assertex(r<count);
  475. return *nodes[r];
  476. }
  477. INode *getNode(rank_t r) const
  478. {
  479. assertex(r<count);
  480. return LINK(nodes[r]);
  481. }
  482. StringBuffer &getText(StringBuffer &text) const
  483. {
  484. if (!count)
  485. return text;
  486. if (count==1)
  487. return nodes[0]->endpoint().getUrlStr(text);
  488. // following is rather slow maybe could do with more direct method with pointers TBD
  489. SocketEndpointArray epa;
  490. for(unsigned i=0;i<count;i++) {
  491. SocketEndpoint ep(nodes[i]->endpoint()); // pretty horrible
  492. epa.append(ep);
  493. }
  494. return epa.getText(text);
  495. }
  496. static IGroup *fromText(const char *s,unsigned defport)
  497. {
  498. SocketEndpointArray epa;
  499. epa.fromText(s,defport);
  500. ForEachItemIn(idx, epa)
  501. {
  502. if (!epa.item(idx).isNull())
  503. return createIGroup(epa);
  504. }
  505. throw MakeStringException(0, "Invalid group %s (all nodes null)", s);
  506. }
  507. void serialize(MemoryBuffer &tgt) const
  508. {
  509. tgt.append(count);
  510. for (rank_t i=0; i<count; i++)
  511. nodes[i]->serialize(tgt);
  512. }
  513. static CGroup *deserialize(MemoryBuffer &src)
  514. {
  515. CGroup *ret = new CGroup(0);
  516. src.read(ret->count);
  517. if (ret->count) {
  518. ret->nodes = ret->count?(INode **)malloc(ret->count*sizeof(INode *)):NULL;
  519. for (rank_t i=0; i<ret->count; i++)
  520. ret->nodes[i] = MPNode::deserialize(src);
  521. }
  522. return ret;
  523. }
  524. void getSocketEndpoints(SocketEndpointArray &sea) const
  525. {
  526. for (rank_t i=0; i<count; i++)
  527. sea.append((SocketEndpoint &)nodes[i]->endpoint());
  528. }
  529. };
  530. class CNodeIterator : implements INodeIterator, public CInterface
  531. {
  532. Linked<IGroup> parent;
  533. rank_t start;
  534. rank_t num;
  535. rank_t pos;
  536. public:
  537. IMPLEMENT_IINTERFACE;
  538. CNodeIterator(IGroup *_parent,rank_t _start,rank_t _num)
  539. : parent(_parent)
  540. {
  541. start = _start;
  542. num = _num;
  543. if ((num==RANK_NULL)||(num+start>parent->ordinality()))
  544. num = (start<parent->ordinality())?parent->ordinality()-start:0;
  545. }
  546. bool first()
  547. {
  548. pos = 0;
  549. return (pos<num);
  550. }
  551. bool next()
  552. {
  553. pos++;
  554. return (pos<num);
  555. }
  556. bool isValid()
  557. {
  558. return (pos<num);
  559. }
  560. INode &query()
  561. {
  562. return parent->queryNode(start+pos);
  563. }
  564. INode &get()
  565. {
  566. return *parent->getNode(start+pos);
  567. }
  568. };
  569. INodeIterator *CGroup::getIterator(rank_t start,rank_t num) const
  570. {
  571. return new CNodeIterator((IGroup *)this,start,num);
  572. }
  573. INode *deserializeINode(MemoryBuffer &src)
  574. {
  575. return MPNode::deserialize(src);
  576. }
  577. INode *createINode(const SocketEndpoint &ep)
  578. {
  579. if (NodeCache)
  580. return NodeCache->lookup(ep);
  581. return new MPNode(ep);
  582. }
  583. INode *createINodeIP(const IpAddress &ip,unsigned short port=0)
  584. {
  585. SocketEndpoint ep(port,ip);
  586. return createINode(ep);
  587. }
  588. INode *createINode(const char *name,unsigned short port)
  589. {
  590. SocketEndpoint ep(name,port);
  591. return createINode(ep);
  592. }
  593. IGroup *createIGroup(rank_t num,INode **nodes)
  594. {
  595. return new CGroup(num,nodes);
  596. }
  597. IGroup *createIGroup(rank_t num,const SocketEndpoint *ep)
  598. {
  599. return new CGroup(num,ep);
  600. }
  601. IGroup *createIGroup(SocketEndpointArray &epa)
  602. {
  603. return new CGroup(epa);
  604. }
  605. IGroup *createIGroup(const char *endpointlist,unsigned short defport)
  606. {
  607. const char *s = endpointlist;
  608. bool oldform=false;
  609. if (s)
  610. while (*s) {
  611. if (*s=='|') {
  612. oldform = true;
  613. break;
  614. }
  615. if (*s==',') {
  616. while (isspace(*s))
  617. s++;
  618. if ((*s=='=')||(*s=='*')) {
  619. oldform = true;
  620. break;
  621. }
  622. }
  623. s++;
  624. }
  625. if (oldform) {
  626. SocketListParser list(endpointlist);
  627. SocketEndpointArray eparray;
  628. list.getSockets(eparray,defport);
  629. return createIGroup(eparray);
  630. }
  631. return CGroup::fromText(endpointlist,defport);
  632. }
  633. IGroup *createIGroupRetry(const char *endpointlist,unsigned short defport, unsigned timeout)
  634. {
  635. CTimeMon t(timeout);
  636. while (!t.timedout())
  637. {
  638. try
  639. {
  640. return createIGroup(endpointlist, defport);
  641. }
  642. catch (IException *e)
  643. {
  644. VStringBuffer errMsg("Failed to resolve group for: %s", endpointlist);
  645. EXCLOG(e, errMsg.str());
  646. e->Release();
  647. }
  648. // on resolve failure, pause for a short time to avoid spinning too fast
  649. Sleep(5);
  650. }
  651. throw makeStringExceptionV(0, "Timedout trying to resolve group: %s", endpointlist);
  652. }
  653. IGroup *deserializeIGroup(MemoryBuffer &src)
  654. {
  655. return CGroup::deserialize(src);
  656. }
  657. void initMyNode(unsigned short port)
  658. {
  659. setNodeCaching(port != 0);
  660. ::Release(MyNode);
  661. MyNode = NULL;
  662. if (port) {
  663. SocketEndpoint ep(port);
  664. MyNode = new MPNode(ep);
  665. if (ep.isLoopBack()) {
  666. DBGLOG("MP Warning - localhost used for MP host address, NIC adaptor not identified");
  667. }
  668. queryNullNode();
  669. }
  670. else
  671. {
  672. ::Release(NullNode);
  673. NullNode = NULL;
  674. }
  675. }
  676. INode *queryMyNode()
  677. {
  678. return MyNode;
  679. }
  680. INode *queryNullNode()
  681. {
  682. if (!NullNode) {
  683. SocketEndpoint ep;
  684. NullNode = new MPNode(ep);
  685. }
  686. return NullNode;
  687. }
  688. void setNodeCaching(bool on)
  689. {
  690. if (on) {
  691. if (!NodeCache)
  692. NodeCache = new MPNodeCache();
  693. }
  694. else {
  695. MPNodeCache *nc = NodeCache;
  696. NodeCache = NULL;
  697. delete nc;
  698. }
  699. }