Merge lp:~epics-core/epics-base/cahuge into lp:~epics-core/epics-base/3.16

Proposed by mdavidsaver
Status: Rejected
Rejected by: mdavidsaver
Proposed branch: lp:~epics-core/epics-base/cahuge
Merge into: lp:~epics-core/epics-base/3.16
Diff against target: 418 lines (+156/-68)
10 files modified
src/ca/client/tcpiiu.cpp (+48/-16)
src/ca/legacy/pcas/generic/casStrmClient.cc (+1/-1)
src/ca/legacy/pcas/generic/clientBufMemoryManager.cpp (+0/-5)
src/ca/legacy/pcas/generic/clientBufMemoryManager.h (+1/-1)
src/ca/legacy/pcas/generic/inBuf.cc (+13/-5)
src/ca/legacy/pcas/generic/inBuf.h (+2/-2)
src/ca/legacy/pcas/generic/outBuf.cc (+11/-5)
src/ca/legacy/pcas/generic/outBuf.h (+1/-1)
src/ioc/rsrv/caservertask.c (+78/-31)
src/ioc/rsrv/server.h (+1/-1)
To merge this branch: bzr merge lp:~epics-core/epics-base/cahuge
Reviewer Review Type Date Requested Status
mdavidsaver superseded Disapprove
Review via email: mp+319759@code.launchpad.net

Description of the change

Relaxation of EPICS_CA_MAX_ARRAY_BYTES. Instead of failing, attempt to handle large messages with a single allocation, if realloc() errors then fail as before. These large allocations aren't pooled, and are free()'d when a client disconnects.

What I've seen (eg. @ nsls2 and frib) is that users always set EPICS_CA_MAX_ARRAY_BYTES to be larger than the largest possible array in a facility (typically a camera image). This leads to inefficient use of buffers as only a small number of PVs over 16KB are actually this large. (as CAJ does this as well, I've wondered how much this behavior contributes to cs-studio's reputation as a memory hog)

This change would allow EPICS_CA_MAX_ARRAY_BYTES to be used as an optimization based on the most common array size.

I've left a hook (the NO_HUGE macro) to disable this in libca and RSRV on based on some condition.

To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote :

F2F meeting 3/14/2017: Change to use a run-time switch instead of NO_HUGE, default setting configurable by target, new behaviour: Don’t use free-list, always allocate large buffers on demand and release when client disconnects.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :
review: Disapprove (superseded)

Unmerged revisions

12748. By mdavidsaver

rsrv: support larger than max. array bytes

12747. By mdavidsaver

pcas: support larger than max array bytes

clientBufMemoryManager already supports allocations
larger than max array bytes, adjust callers inBuf/outBuf
to actually request larger allocations.

12746. By mdavidsaver

ca: support alloc larger than max array bytes

automatically try to allocate a custom buffer
when a message larger than ca max array bytes
is encountered.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/ca/client/tcpiiu.cpp'
--- src/ca/client/tcpiiu.cpp 2016-10-20 20:32:06 +0000
+++ src/ca/client/tcpiiu.cpp 2017-03-13 23:51:22 +0000
@@ -26,6 +26,9 @@
2626
27#include <stdexcept>27#include <stdexcept>
28#include <string>28#include <string>
29
30#include <stdlib.h>
31
29#include "errlog.h"32#include "errlog.h"
3033
31#define epicsExportSharedSymbols34#define epicsExportSharedSymbols
@@ -1023,12 +1026,15 @@
10231026
1024 // free message body cache1027 // free message body cache
1025 if ( this->pCurData ) {1028 if ( this->pCurData ) {
1026 if ( this->curDataMax == MAX_TCP ) {1029 if ( this->curDataMax <= MAX_TCP ) {
1027 this->cacRef.releaseSmallBufferTCP ( this->pCurData );1030 this->cacRef.releaseSmallBufferTCP ( this->pCurData );
1028 }1031 }
1029 else {1032 else if ( this->curDataMax <= cacRef.largeBufferSizeTCP()) {
1030 this->cacRef.releaseLargeBufferTCP ( this->pCurData );1033 this->cacRef.releaseLargeBufferTCP ( this->pCurData );
1031 }1034 }
1035 else {
1036 free ( this->pCurData );
1037 }
1032 }1038 }
1033}1039}
10341040
@@ -1197,18 +1203,44 @@
1197 // make sure we have a large enough message body cache1203 // make sure we have a large enough message body cache
1198 //1204 //
1199 if ( this->curMsg.m_postsize > this->curDataMax ) {1205 if ( this->curMsg.m_postsize > this->curDataMax ) {
1200 if ( this->curDataMax == MAX_TCP && 1206 assert (this->curMsg.m_postsize > MAX_TCP);
1201 this->cacRef.largeBufferSizeTCP() >= this->curMsg.m_postsize ) {1207
1202 char * pBuf = this->cacRef.allocateLargeBufferTCP ();1208 char * newbuf = NULL;
1203 if ( pBuf ) {1209 arrayElementCount newsize;
1210
1211 if ( this->curMsg.m_postsize <= this->cacRef.largeBufferSizeTCP() ) {
1212 newbuf = this->cacRef.allocateLargeBufferTCP ();
1213 newsize = this->cacRef.largeBufferSizeTCP();
1214 }
1215#ifndef NO_HUGE
1216 else {
1217 // round size up to multiple of 4K
1218 newsize = ((this->curMsg.m_postsize-1)|0xfff)+1;
1219
1220 if (this->curDataMax > this->cacRef.largeBufferSizeTCP()) {
1221 // expand existing huge buffer
1222 newbuf = (char*)realloc(this->pCurData, newsize);
1223 } else {
1224 // trade up from small or large
1225 newbuf = (char*)malloc(newsize);
1226 }
1227 }
1228#endif
1229
1230 if ( newbuf) {
1231 if (this->curDataMax <= MAX_TCP) {
1204 this->cacRef.releaseSmallBufferTCP ( this->pCurData );1232 this->cacRef.releaseSmallBufferTCP ( this->pCurData );
1205 this->pCurData = pBuf;1233 } else if (this->curDataMax <= this->cacRef.largeBufferSizeTCP()) {
1206 this->curDataMax = this->cacRef.largeBufferSizeTCP ();1234 this->cacRef.releaseLargeBufferTCP ( this->pCurData );
1207 }1235 } else {
1208 else {1236 // called realloc()
1209 this->printFormated ( mgr.cbGuard,1237 }
1210 "CAC: not enough memory for message body cache (ignoring response message)\n");1238 this->pCurData = newbuf;
1211 }1239 this->curDataMax = newsize;
1240
1241 } else {
1242 this->printFormated ( mgr.cbGuard,
1243 "CAC: not enough memory for message body cache (ignoring response message)\n");
1212 }1244 }
1213 }1245 }
12141246
@@ -1426,7 +1458,7 @@
1426 }1458 }
1427 arrayElementCount maxBytes;1459 arrayElementCount maxBytes;
1428 if ( CA_V49 ( this->minorProtocolVersion ) ) {1460 if ( CA_V49 ( this->minorProtocolVersion ) ) {
1429 maxBytes = this->cacRef.largeBufferSizeTCP ();1461 maxBytes = 0xfffffff0;
1430 }1462 }
1431 else {1463 else {
1432 maxBytes = MAX_TCP;1464 maxBytes = MAX_TCP;
@@ -1537,7 +1569,7 @@
1537 guard, CA_V413(this->minorProtocolVersion) );1569 guard, CA_V413(this->minorProtocolVersion) );
1538 arrayElementCount maxBytes;1570 arrayElementCount maxBytes;
1539 if ( CA_V49 ( this->minorProtocolVersion ) ) {1571 if ( CA_V49 ( this->minorProtocolVersion ) ) {
1540 maxBytes = this->cacRef.largeBufferSizeTCP ();1572 maxBytes = 0xfffffff0;
1541 }1573 }
1542 else {1574 else {
1543 maxBytes = MAX_TCP;1575 maxBytes = MAX_TCP;
@@ -1584,7 +1616,7 @@
1584 guard, CA_V413(this->minorProtocolVersion) );1616 guard, CA_V413(this->minorProtocolVersion) );
1585 arrayElementCount maxBytes;1617 arrayElementCount maxBytes;
1586 if ( CA_V49 ( this->minorProtocolVersion ) ) {1618 if ( CA_V49 ( this->minorProtocolVersion ) ) {
1587 maxBytes = this->cacRef.largeBufferSizeTCP ();1619 maxBytes = 0xfffffff0;
1588 }1620 }
1589 else {1621 else {
1590 maxBytes = MAX_TCP;1622 maxBytes = MAX_TCP;
15911623
=== modified file 'src/ca/legacy/pcas/generic/casStrmClient.cc'
--- src/ca/legacy/pcas/generic/casStrmClient.cc 2016-12-07 22:36:58 +0000
+++ src/ca/legacy/pcas/generic/casStrmClient.cc 2017-03-13 23:51:22 +0000
@@ -205,7 +205,7 @@
205 if ( bytesLeft < msgSize ) {205 if ( bytesLeft < msgSize ) {
206 status = S_cas_success;206 status = S_cas_success;
207 if ( msgSize > this->in.bufferSize() ) {207 if ( msgSize > this->in.bufferSize() ) {
208 this->in.expandBuffer ();208 this->in.expandBuffer (msgSize);
209 // msg to large - set up message drain209 // msg to large - set up message drain
210 if ( msgSize > this->in.bufferSize() ) {210 if ( msgSize > this->in.bufferSize() ) {
211 caServerI::dumpMsg ( this->pHostName, this->pUserName, & msgTmp, 0, 211 caServerI::dumpMsg ( this->pHostName, this->pUserName, & msgTmp, 0,
212212
=== modified file 'src/ca/legacy/pcas/generic/clientBufMemoryManager.cpp'
--- src/ca/legacy/pcas/generic/clientBufMemoryManager.cpp 2016-05-22 03:43:09 +0000
+++ src/ca/legacy/pcas/generic/clientBufMemoryManager.cpp 2017-03-13 23:51:22 +0000
@@ -16,11 +16,6 @@
16#define epicsExportSharedSymbols16#define epicsExportSharedSymbols
17#include "clientBufMemoryManager.h"17#include "clientBufMemoryManager.h"
1818
19bufSizeT clientBufMemoryManager::maxSize () const
20{
21 return bufferFactory.largeBufferSize ();
22}
23
24casBufferParm clientBufMemoryManager::allocate ( bufSizeT newMinSize )19casBufferParm clientBufMemoryManager::allocate ( bufSizeT newMinSize )
25{20{
26 casBufferParm parm;21 casBufferParm parm;
2722
=== modified file 'src/ca/legacy/pcas/generic/clientBufMemoryManager.h'
--- src/ca/legacy/pcas/generic/clientBufMemoryManager.h 2003-02-12 19:06:15 +0000
+++ src/ca/legacy/pcas/generic/clientBufMemoryManager.h 2017-03-13 23:51:22 +0000
@@ -39,9 +39,9 @@
3939
40class clientBufMemoryManager {40class clientBufMemoryManager {
41public:41public:
42 //! @throws std::bad_alloc on failure
42 casBufferParm allocate ( bufSizeT newMinSize );43 casBufferParm allocate ( bufSizeT newMinSize );
43 void release ( char * pBuf, bufSizeT bufSize );44 void release ( char * pBuf, bufSizeT bufSize );
44 bufSizeT maxSize () const;
45private:45private:
46 casBufferFactory bufferFactory;46 casBufferFactory bufferFactory;
47};47};
4848
=== modified file 'src/ca/legacy/pcas/generic/inBuf.cc'
--- src/ca/legacy/pcas/generic/inBuf.cc 2016-05-22 12:38:18 +0000
+++ src/ca/legacy/pcas/generic/inBuf.cc 2017-03-13 23:51:22 +0000
@@ -13,6 +13,8 @@
13 * 505 665 183113 * 505 665 1831
14 */14 */
1515
16#include <stdexcept>
17
16#include <stdio.h>18#include <stdio.h>
17#include <string.h>19#include <string.h>
1820
@@ -155,11 +157,17 @@
155 }157 }
156}158}
157159
158void inBuf::expandBuffer ()160void inBuf::expandBuffer (bufSizeT needed)
159{161{
160 bufSizeT max = this->memMgr.maxSize();162 if (needed > bufSize) {
161 if ( this->bufSize < max ) {163 casBufferParm bufParm;
162 casBufferParm bufParm = this->memMgr.allocate ( max );164 try {
165 bufParm = this->memMgr.allocate ( needed );
166 } catch (std::bad_alloc& e) {
167 // caller must check that buffer size has expended
168 return;
169 }
170
163 bufSizeT unprocessedBytes = this->bytesPresent ();171 bufSizeT unprocessedBytes = this->bytesPresent ();
164 memcpy ( bufParm.pBuf, &this->pBuf[this->nextReadIndex], unprocessedBytes );172 memcpy ( bufParm.pBuf, &this->pBuf[this->nextReadIndex], unprocessedBytes );
165 this->bytesInBuffer = unprocessedBytes;173 this->bytesInBuffer = unprocessedBytes;
@@ -170,7 +178,7 @@
170 }178 }
171}179}
172180
173unsigned inBuf::bufferSize () const181bufSizeT inBuf::bufferSize() const
174{182{
175 return this->bufSize;183 return this->bufSize;
176}184}
177185
=== modified file 'src/ca/legacy/pcas/generic/inBuf.h'
--- src/ca/legacy/pcas/generic/inBuf.h 2012-04-12 16:13:50 +0000
+++ src/ca/legacy/pcas/generic/inBuf.h 2017-03-13 23:51:22 +0000
@@ -82,8 +82,8 @@
82 //82 //
83 const inBufCtx pushCtx ( bufSizeT headerSize, bufSizeT bodySize );83 const inBufCtx pushCtx ( bufSizeT headerSize, bufSizeT bodySize );
84 bufSizeT popCtx ( const inBufCtx & ); // returns actual size84 bufSizeT popCtx ( const inBufCtx & ); // returns actual size
85 unsigned bufferSize () const;85 bufSizeT bufferSize () const;
86 void expandBuffer ();86 void expandBuffer (bufSizeT needed);
87private:87private:
88 class inBufClient & client;88 class inBufClient & client;
89 class clientBufMemoryManager & memMgr;89 class clientBufMemoryManager & memMgr;
9090
=== modified file 'src/ca/legacy/pcas/generic/outBuf.cc'
--- src/ca/legacy/pcas/generic/outBuf.cc 2016-05-22 12:38:18 +0000
+++ src/ca/legacy/pcas/generic/outBuf.cc 2017-03-13 23:51:22 +0000
@@ -59,7 +59,7 @@
59 msgsize = CA_MESSAGE_ALIGN ( msgsize );59 msgsize = CA_MESSAGE_ALIGN ( msgsize );
6060
61 if ( msgsize > this->bufSize ) {61 if ( msgsize > this->bufSize ) {
62 this->expandBuffer ();62 this->expandBuffer (msgsize);
63 if ( msgsize > this->bufSize ) {63 if ( msgsize > this->bufSize ) {
64 return S_cas_hugeRequest;64 return S_cas_hugeRequest;
65 }65 }
@@ -316,11 +316,17 @@
316 }316 }
317}317}
318318
319void outBuf::expandBuffer ()319void outBuf::expandBuffer (bufSizeT needed)
320{320{
321 bufSizeT max = this->memMgr.maxSize();321 if (needed > bufSize) {
322 if ( this->bufSize < max ) {322 casBufferParm bufParm;
323 casBufferParm bufParm = this->memMgr.allocate ( max );323 try {
324 bufParm = this->memMgr.allocate ( needed );
325 } catch (std::bad_alloc& e) {
326 // caller must check that buffer size has expended
327 return;
328 }
329
324 memcpy ( bufParm.pBuf, this->pBuf, this->stack );330 memcpy ( bufParm.pBuf, this->pBuf, this->stack );
325 this->memMgr.release ( this->pBuf, this->bufSize );331 this->memMgr.release ( this->pBuf, this->bufSize );
326 this->pBuf = bufParm.pBuf;332 this->pBuf = bufParm.pBuf;
327333
=== modified file 'src/ca/legacy/pcas/generic/outBuf.h'
--- src/ca/legacy/pcas/generic/outBuf.h 2012-04-12 16:13:50 +0000
+++ src/ca/legacy/pcas/generic/outBuf.h 2017-03-13 23:51:22 +0000
@@ -122,7 +122,7 @@
122 bufSizeT stack;122 bufSizeT stack;
123 unsigned ctxRecursCount;123 unsigned ctxRecursCount;
124124
125 void expandBuffer ();125 void expandBuffer (bufSizeT needed);
126126
127 outBuf ( const outBuf & );127 outBuf ( const outBuf & );
128 outBuf & operator = ( const outBuf & );128 outBuf & operator = ( const outBuf & );
129129
=== modified file 'src/ioc/rsrv/caservertask.c'
--- src/ioc/rsrv/caservertask.c 2017-01-23 23:20:51 +0000
+++ src/ioc/rsrv/caservertask.c 2017-03-13 23:51:22 +0000
@@ -1060,6 +1060,9 @@
1060 else if ( client->send.type == mbtLargeTCP ) {1060 else if ( client->send.type == mbtLargeTCP ) {
1061 freeListFree ( rsrvLargeBufFreeListTCP, client->send.buf );1061 freeListFree ( rsrvLargeBufFreeListTCP, client->send.buf );
1062 }1062 }
1063 else if (client->send.type == mbtHugeTCP ) {
1064 free ( client->send.buf );
1065 }
1063 else {1066 else {
1064 errlogPrintf ( "CAS: Corrupt send buffer free list type code=%u during client cleanup?\n",1067 errlogPrintf ( "CAS: Corrupt send buffer free list type code=%u during client cleanup?\n",
1065 client->send.type );1068 client->send.type );
@@ -1072,6 +1075,9 @@
1072 else if ( client->recv.type == mbtLargeTCP ) {1075 else if ( client->recv.type == mbtLargeTCP ) {
1073 freeListFree ( rsrvLargeBufFreeListTCP, client->recv.buf );1076 freeListFree ( rsrvLargeBufFreeListTCP, client->recv.buf );
1074 }1077 }
1078 else if (client->recv.type == mbtHugeTCP ) {
1079 free ( client->recv.buf );
1080 }
1075 else {1081 else {
1076 errlogPrintf ( "CAS: Corrupt recv buffer free list type code=%u during client cleanup?\n",1082 errlogPrintf ( "CAS: Corrupt recv buffer free list type code=%u during client cleanup?\n",
1077 client->send.type );1083 client->send.type );
@@ -1301,43 +1307,84 @@
1301 taskwdInsert ( pClient->tid, NULL, NULL );1307 taskwdInsert ( pClient->tid, NULL, NULL );
1302}1308}
13031309
1310static
1311void casExpandBuffer ( struct message_buffer *buf, ca_uint32_t size, int sendbuf )
1312{
1313 char *newbuf = NULL;
1314 unsigned newsize;
1315 enum messageBufferType newtype;
1316
1317 assert (size > MAX_TCP);
1318
1319 if ( size <= buf->maxstk || buf->type == mbtUDP ) return;
1320
1321 /* try to alloc new buffer */
1322 if (size <= MAX_TCP) {
1323 return; /* shouldn't happen */
1324 } else if (size <= rsrvSizeofLargeBufTCP) {
1325 newbuf = freeListCalloc ( rsrvLargeBufFreeListTCP );
1326 newsize = rsrvSizeofLargeBufTCP;
1327 newtype = mbtLargeTCP;
1328 }
1329#ifndef NO_HUGE
1330 else {
1331 size = ((size-1)|0xfff)+1;
1332
1333 if (buf->type==mbtHugeTCP)
1334 newbuf = realloc (buf->buf, size);
1335 else
1336 newbuf = malloc (size);
1337 newtype = mbtHugeTCP;
1338 newsize = size;
1339 }
1340#endif
1341
1342 if (newbuf) {
1343 /* copy existing buffer */
1344 if (sendbuf) {
1345 /* send buffer uses [0, stk) */
1346 if (buf->type==mbtHugeTCP) {
1347 /* realloc already copied */
1348 } else {
1349 memcpy ( newbuf, buf->buf, buf->stk );
1350 }
1351 } else {
1352 /* recv buffer uses [stk, cnt) */
1353 unsigned used;
1354 assert ( buf->cnt >= buf->stk );
1355 used = buf->cnt - buf->stk;
1356
1357 /* buf->buf may be the same as newbuf if realloc() used */
1358 memmove ( newbuf, &buf->buf[buf->stk], used );
1359
1360 buf->cnt = used;
1361 buf->stk = 0;
1362
1363 }
1364
1365 /* free existing buffer */
1366 if(buf->type==mbtSmallTCP) {
1367 freeListFree ( rsrvSmallBufFreeListTCP, buf->buf );
1368 } else if(buf->type==mbtLargeTCP) {
1369 freeListFree ( rsrvLargeBufFreeListTCP, buf->buf );
1370 } else {
1371 /* realloc() already free()'d if necessary */
1372 }
1373
1374 buf->buf = newbuf;
1375 buf->type = newtype;
1376 buf->maxstk = newsize;
1377 }
1378}
1379
1304void casExpandSendBuffer ( struct client *pClient, ca_uint32_t size )1380void casExpandSendBuffer ( struct client *pClient, ca_uint32_t size )
1305{1381{
1306 if ( pClient->send.type == mbtSmallTCP && rsrvSizeofLargeBufTCP > MAX_TCP1382 casExpandBuffer (&pClient->send, size, 1);
1307 && size <= rsrvSizeofLargeBufTCP ) {
1308 int spaceAvailOnFreeList = freeListItemsAvail ( rsrvLargeBufFreeListTCP ) > 0;
1309 if ( osiSufficentSpaceInPool(rsrvSizeofLargeBufTCP) || spaceAvailOnFreeList ) {
1310 char *pNewBuf = ( char * ) freeListCalloc ( rsrvLargeBufFreeListTCP );
1311 if ( pNewBuf ) {
1312 memcpy ( pNewBuf, pClient->send.buf, pClient->send.stk );
1313 freeListFree ( rsrvSmallBufFreeListTCP, pClient->send.buf );
1314 pClient->send.buf = pNewBuf;
1315 pClient->send.maxstk = rsrvSizeofLargeBufTCP;
1316 pClient->send.type = mbtLargeTCP;
1317 }
1318 }
1319 }
1320}1383}
13211384
1322void casExpandRecvBuffer ( struct client *pClient, ca_uint32_t size )1385void casExpandRecvBuffer ( struct client *pClient, ca_uint32_t size )
1323{1386{
1324 if ( pClient->recv.type == mbtSmallTCP && rsrvSizeofLargeBufTCP > MAX_TCP1387 casExpandBuffer (&pClient->recv, size, 0);
1325 && size <= rsrvSizeofLargeBufTCP) {
1326 int spaceAvailOnFreeList = freeListItemsAvail ( rsrvLargeBufFreeListTCP ) > 0;
1327 if ( osiSufficentSpaceInPool(rsrvSizeofLargeBufTCP) || spaceAvailOnFreeList ) {
1328 char *pNewBuf = ( char * ) freeListCalloc ( rsrvLargeBufFreeListTCP );
1329 if ( pNewBuf ) {
1330 assert ( pClient->recv.cnt >= pClient->recv.stk );
1331 memcpy ( pNewBuf, &pClient->recv.buf[pClient->recv.stk], pClient->recv.cnt - pClient->recv.stk );
1332 freeListFree ( rsrvSmallBufFreeListTCP, pClient->recv.buf );
1333 pClient->recv.buf = pNewBuf;
1334 pClient->recv.cnt = pClient->recv.cnt - pClient->recv.stk;
1335 pClient->recv.stk = 0u;
1336 pClient->recv.maxstk = rsrvSizeofLargeBufTCP;
1337 pClient->recv.type = mbtLargeTCP;
1338 }
1339 }
1340 }
1341}1388}
13421389
1343/*1390/*
13441391
=== modified file 'src/ioc/rsrv/server.h'
--- src/ioc/rsrv/server.h 2016-05-13 18:12:08 +0000
+++ src/ioc/rsrv/server.h 2017-03-13 23:51:22 +0000
@@ -60,7 +60,7 @@
60 * Eight-byte alignment is required by the Sparc 5 and other RISC60 * Eight-byte alignment is required by the Sparc 5 and other RISC
61 * processors.61 * processors.
62 */62 */
63enum messageBufferType { mbtUDP, mbtSmallTCP, mbtLargeTCP };63enum messageBufferType { mbtUDP, mbtSmallTCP, mbtLargeTCP, mbtHugeTCP };
64struct message_buffer {64struct message_buffer {
65 char *buf;65 char *buf;
66 unsigned stk;66 unsigned stk;

Subscribers

People subscribed via source and target branches