- inserted command statistic

- add runscript parameter to environment object
- added Arg2Tcl0 function
This commit is contained in:
zolliker
2006-06-20 13:28:17 +00:00
parent bd533e6131
commit f88f48fca9
15 changed files with 334 additions and 32 deletions

View File

@ -57,6 +57,7 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <tcl.h> #include <tcl.h>
#include <time.h>
#include "fortify.h" #include "fortify.h"
#include "sics.h" #include "sics.h"
#include "splitter.h" #include "splitter.h"
@ -151,6 +152,7 @@ static void freeList(int listID);
pNew->pData = pData; pNew->pData = pData;
pNew->pNext = NULL; pNew->pNext = NULL;
pNew->startupOnly = startupOnly; pNew->startupOnly = startupOnly;
pNew->stat = StatisticsNew(pBueffel);
/* find end of list */ /* find end of list */
tail = NULL; tail = NULL;
@ -232,10 +234,14 @@ static void freeList(int listID);
{ {
pInterp->pCList = pVictim->pNext; pInterp->pCList = pVictim->pNext;
} }
if (pVictim->stat) {
StatisticsKill(pVictim->stat);
}
free(pVictim); free(pVictim);
return 1; return 1;
} }
#define MAXLEN 256 #define MAXLEN 256
#define MAXCOM 50 #define MAXCOM 50
extern char *stptok(char *s, char *tok, unsigned int toklen, char *brk); extern char *stptok(char *s, char *tok, unsigned int toklen, char *brk);
@ -252,6 +258,7 @@ extern char *SkipSpace(char *pPtr);
char *pPtr; char *pPtr;
char **argv = NULL; char **argv = NULL;
commandContext comCon; commandContext comCon;
Statistics *old;
assert(self); assert(self);
@ -307,7 +314,9 @@ extern char *SkipSpace(char *pPtr);
Tcl_ResetResult((Tcl_Interp *)self->pTcl); Tcl_ResetResult((Tcl_Interp *)self->pTcl);
MacroPush(pCon); MacroPush(pCon);
pCon->conStatus = 0; pCon->conStatus = 0;
old = StatisticsBegin(pCommand->stat);
iRet = pCommand->OFunc(pCon, self, pCommand->pData, argc, argv); iRet = pCommand->OFunc(pCon, self, pCommand->pData, argc, argv);
StatisticsEnd(old);
/* If a task is registered with the dev exec then conStatus is HWBusy*/ /* If a task is registered with the dev exec then conStatus is HWBusy*/
if (pCon->conStatus != HWBusy) { if (pCon->conStatus != HWBusy) {
comCon = SCGetContext(pCon); comCon = SCGetContext(pCon);
@ -444,29 +453,40 @@ extern char *SkipSpace(char *pPtr);
void DeleteInterp(SicsInterp *self) void DeleteInterp(SicsInterp *self)
{ {
CommandList *pCurrent = NULL; CommandList *pCurrent = NULL;
CommandList *pTemp; CommandList *pTemp, *tail;
Tcl_Interp *pTcl = NULL; Tcl_Interp *pTcl = NULL;
int i; int i;
assert(self); assert(self);
self->iDeleting = 1; self->iDeleting = 1;
/* delete Commandlist */ /* find end of list */
tail = NULL;
pCurrent = self->pCList; pCurrent = self->pCList;
while(pCurrent) while(pCurrent != NULL)
{ {
if(pCurrent->KFunc) tail = pCurrent;
pCurrent = pCurrent->pNext;
}
/* delete Commandlist (reversed order) */
if (tail) {
pCurrent = tail;
while(pCurrent)
{ {
pCurrent->KFunc(pCurrent->pData); if(pCurrent->KFunc)
{
pCurrent->KFunc(pCurrent->pData);
}
if(pCurrent->pName)
{
/* printf("Deleting %s\n",pCurrent->pName); */
free(pCurrent->pName);
}
pTemp = pCurrent->pPrevious;
free(pCurrent);
pCurrent = pTemp;
} }
if(pCurrent->pName)
{
/* printf("Deleting %s\n",pCurrent->pName); */
free(pCurrent->pName);
}
pTemp = pCurrent->pNext;
free(pCurrent);
pCurrent = pTemp;
} }
FreeAliasList(&self->AList); /* M.Z. */ FreeAliasList(&self->AList); /* M.Z. */

View File

@ -10,6 +10,7 @@
#ifndef SICSINTERPRETER #ifndef SICSINTERPRETER
#define SICSINTERPRETER #define SICSINTERPRETER
#include "Scommon.h" #include "Scommon.h"
#include "statistics.h"
#include <tcl.h> #include <tcl.h>
/* M.Z. */ /* M.Z. */
#include "definealias.i" #include "definealias.i"
@ -31,6 +32,7 @@ typedef struct __Clist {
struct __Clist *pNext; struct __Clist *pNext;
struct __Clist *pPrevious; struct __Clist *pPrevious;
int startupOnly; int startupOnly;
Statistics *stat;
} CommandList; } CommandList;
typedef struct __SINTER typedef struct __SINTER

View File

@ -71,13 +71,29 @@
static long EVIDrive(void *pData, SConnection *pCon, float fVal) static long EVIDrive(void *pData, SConnection *pCon, float fVal)
{ {
pEVControl self = NULL; pEVControl self = NULL;
int iRet, iCode, i, iFix; int iRet, iCode, i, iFix, savedStatus;
char pError[132], pBueffel[256]; char pError[132], pBueffel[256];
Tcl_Interp *pTcl = NULL;
self = (pEVControl)pData; self = (pEVControl)pData;
assert(self); assert(self);
assert(pCon); assert(pCon);
if (self->runScript != NULL) {
savedStatus = GetStatus();
SetStatus(eBatch);
pTcl = InterpGetTcl(pServ->pSics);
snprintf(pBueffel, sizeof(pBueffel), "%s %f", self->runScript, fVal);
iRet = Tcl_Eval(pTcl,pBueffel);
SetStatus(savedStatus);
if(iRet != TCL_OK)
{
SCPrintf(pCon, eError,
"ERROR: %s while processing runscript for %s",
pTcl->result,self->pName);
}
}
self->fTarget = fVal; self->fTarget = fVal;
self->eMode = EVDrive; self->eMode = EVDrive;
self->iStop = 0; self->iStop = 0;
@ -848,6 +864,10 @@ static void ErrReport(pEVControl self)
{ {
free(self->creationArgs); free(self->creationArgs);
} }
if (self->runScript != NULL)
{
free(self->runScript);
}
free(self); free(self);
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
@ -855,7 +875,7 @@ static void ErrReport(pEVControl self)
{ {
char pBueffel[256], pError[132]; char pBueffel[256], pError[132];
int iRet; int iRet;
assert(self); assert(self);
assert(pCon); assert(pCon);
@ -1025,7 +1045,14 @@ static void ErrReport(pEVControl self)
snprintf(pBueffel,255,"%s.errorScript = UNDEFINED", self->pName); snprintf(pBueffel,255,"%s.errorScript = UNDEFINED", self->pName);
} }
SCWrite(pCon,pBueffel, eValue); SCWrite(pCon,pBueffel, eValue);
if(self->runScript != NULL)
{
SCPrintf(pCon, eValue, "%s.runScript = %s", self->pName, self->runScript);
}
else
{
SCPrintf(pCon, eValue, "%s.runScript = none", self->pName);
}
return 1; return 1;
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
@ -1161,10 +1188,10 @@ static void ErrReport(pEVControl self)
} }
else /* parameter request */ else /* parameter request */
{ {
strtolower(argv[1]);
/* /*
catch case of errorScript catch case of errorScript
*/ */
strtolower(argv[1]);
if(strcmp(argv[1],"errorscript") == 0) if(strcmp(argv[1],"errorscript") == 0)
{ {
if(self->errorScript != NULL) if(self->errorScript != NULL)
@ -1180,6 +1207,22 @@ static void ErrReport(pEVControl self)
SCWrite(pCon,pBueffel,eValue); SCWrite(pCon,pBueffel,eValue);
return 1; return 1;
} }
/*
catch case of runScript
*/
if(strcmp(argv[1],"runscript") == 0)
{
if(self->runScript != NULL)
{
SCPrintf(pCon, eValue,"%s.runScript = %s",self->pName,
self->runScript);
}
else
{
SCPrintf(pCon, eValue,"%s.runScript = none",self->pName);
}
return 1;
}
/* /*
catch case for drivername catch case for drivername
*/ */
@ -1208,10 +1251,10 @@ static void ErrReport(pEVControl self)
} }
else /* try to set parameter */ else /* try to set parameter */
{ {
strtolower(argv[1]);
/* /*
first catch case of errorScript first catch case of errorScript
*/ */
strtolower(argv[1]);
if(strcmp(argv[1],"errorscript") == 0) if(strcmp(argv[1],"errorscript") == 0)
{ {
if(self->errorScript != NULL) if(self->errorScript != NULL)
@ -1223,6 +1266,24 @@ static void ErrReport(pEVControl self)
SCparChange(pCon); SCparChange(pCon);
return 1; return 1;
} }
/*
catch case of runScript
*/
if(strcmp(argv[1],"runscript") == 0)
{
if(self->runScript != NULL)
{
free(self->runScript);
}
if (strcasecmp(argv[2],"none") == 0) {
self->runScript = NULL;
} else {
self->runScript = Arg2Tcl(argc-2,&argv[2],NULL,0);
}
SCSendOK(pCon);
SCparChange(pCon);
return 1;
}
iRet = Tcl_GetDouble(pSics->pTcl,argv[2],&dVal); iRet = Tcl_GetDouble(pSics->pTcl,argv[2],&dVal);
if(iRet != TCL_OK) if(iRet != TCL_OK)
{ {
@ -1263,6 +1324,9 @@ static int EVSaveStatus(void *pData, char *name, FILE *fil)
if(evc->errorScript != NULL) { if(evc->errorScript != NULL) {
fprintf(fil, " %s errorScript %s\n", evc->pName, evc->errorScript); fprintf(fil, " %s errorScript %s\n", evc->pName, evc->errorScript);
} }
if(evc->runScript != NULL) {
fprintf(fil, " %s runScript %s\n", evc->pName, evc->runScript);
}
} }
fprintf(fil, "}\n"); fprintf(fil, "}\n");
} }

View File

@ -54,6 +54,7 @@ $\langle$evdata {\footnotesize ?}$\rangle\equiv$
\mbox{}\verb@ int iStop;@\\ \mbox{}\verb@ int iStop;@\\
\mbox{}\verb@ SCStore conn;@\\ \mbox{}\verb@ SCStore conn;@\\
\mbox{}\verb@ char *creationArgs;@\\ \mbox{}\verb@ char *creationArgs;@\\
\mbox{}\verb@ char *runScript;@\\
\mbox{}\verb@ void *pPrivate;@\\ \mbox{}\verb@ void *pPrivate;@\\
\mbox{}\verb@ void (*KillPrivate)(void *pData);@\\ \mbox{}\verb@ void (*KillPrivate)(void *pData);@\\
\mbox{}\verb@ } EVControl;@\\ \mbox{}\verb@ } EVControl;@\\
@ -88,6 +89,10 @@ holding the logging information. Then there is a switch, iWarned, which is
used to prevent execessive output on environment controller error handling. used to prevent execessive output on environment controller error handling.
iTcl is a boolean stating if the driver used is a proper C language driver iTcl is a boolean stating if the driver used is a proper C language driver
or a Tcl driver. or a Tcl driver.
creationArgs are the arguments needed to recreate the device. runScript
is a script called on every run or drive command. This script is intended
to set control parameters depending on the targetValue. The script is
called with the target temperature as argument.
This is followed by the void pointer for use by a derived This is followed by the void pointer for use by a derived
class. KillPrivate is a pointer to a function capable of deleting pPrivate class. KillPrivate is a pointer to a function capable of deleting pPrivate
properly. properly.

View File

@ -49,6 +49,7 @@ used by EVControl:
int iStop; int iStop;
SCStore conn; SCStore conn;
char *creationArgs; char *creationArgs;
char *runScript;
void *pPrivate; void *pPrivate;
void (*KillPrivate)(void *pData); void (*KillPrivate)(void *pData);
} EVControl; } EVControl;
@ -76,6 +77,10 @@ holding the logging information. Then there is a switch, iWarned, which is
used to prevent execessive output on environment controller error handling. used to prevent execessive output on environment controller error handling.
iTcl is a boolean stating if the driver used is a proper C language driver iTcl is a boolean stating if the driver used is a proper C language driver
or a Tcl driver. or a Tcl driver.
creationArgs are the arguments needed to recreate the device. runScript
is a script called on every run or drive command. This script is intended
to set control parameters depending on the targetValue. The script is
called with the target temperature as argument.
This is followed by the void pointer for use by a derived This is followed by the void pointer for use by a derived
class. KillPrivate is a pointer to a function capable of deleting pPrivate class. KillPrivate is a pointer to a function capable of deleting pPrivate
properly. properly.

View File

@ -132,7 +132,8 @@
char *lastCommand = NULL, comBuffer[132]; char *lastCommand = NULL, comBuffer[132];
int iRet = 0,i; int iRet = 0,i;
int iMacro; int iMacro;
Statistics *old;
/* get the datastructures */ /* get the datastructures */
pSics = (struct __SicsUnknown *)pData; pSics = (struct __SicsUnknown *)pData;
assert(pSics); assert(pSics);
@ -181,7 +182,9 @@
/* invoke */ /* invoke */
iMacro = SCinMacro(pCon); iMacro = SCinMacro(pCon);
SCsetMacro(pCon,1); SCsetMacro(pCon,1);
old=StatisticsBegin(pCommand->stat);
iRet = pCommand->OFunc(pCon,pSinter,pCommand->pData,margc, myarg); iRet = pCommand->OFunc(pCon,pSinter,pCommand->pData,margc, myarg);
StatisticsEnd(old);
SCsetMacro(pCon,iMacro); SCsetMacro(pCon,iMacro);
/* /*
lastUnkown gets deeply stacked with each SICS command exec'd. lastUnkown gets deeply stacked with each SICS command exec'd.
@ -881,7 +884,7 @@ static int ProtectedExec(ClientData clientData, Tcl_Interp *interp,
} }
/* make a string */ /* make a string */
pCommand = Arg2Tcl(argc,argv,pBueffel,sizeof(pBueffel)); pCommand = Arg2Tcl0(argc-1,argv+1,pBueffel,sizeof(pBueffel),self->command);
if (!pCommand) { if (!pCommand) {
SCWrite(pCon, "ERROR: no more memory", eError); SCWrite(pCon, "ERROR: no more memory", eError);
return 0; return 0;

View File

@ -30,7 +30,7 @@ SOBJ = network.o ifile.o conman.o SCinter.o splitter.o passwd.o \
s_rnge.o sig_die.o gpibcontroller.o $(NIOBJ) mcreader.o mccontrol.o\ s_rnge.o sig_die.o gpibcontroller.o $(NIOBJ) mcreader.o mccontrol.o\
hmdata.o nxscript.o tclintimpl.o sicsdata.o mcstascounter.o \ hmdata.o nxscript.o tclintimpl.o sicsdata.o mcstascounter.o \
mcstashm.o initializer.o remob.o tclmotdriv.o protocol.o \ mcstashm.o initializer.o remob.o tclmotdriv.o protocol.o \
sinfox.o sicslist.o cone.o sinfox.o sicslist.o cone.o statistics.o
MOTOROBJ = motor.o simdriv.o MOTOROBJ = motor.o simdriv.o
COUNTEROBJ = countdriv.o simcter.o counter.o COUNTEROBJ = countdriv.o simcter.o counter.o

1
ofac.c
View File

@ -410,6 +410,7 @@
/* insert here initialization routines ... */ /* insert here initialization routines ... */
INIT(SiteInit); /* site specific initializations */ INIT(SiteInit); /* site specific initializations */
INIT(StatisticsInit);
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/

View File

@ -534,7 +534,6 @@ int RemobAction(SConnection *pCon, SicsInterp *pSics, void *pData,
float fValue; float fValue;
long lID; long lID;
char *endp; char *endp;
char *argv0;
char *cmd; char *cmd;
/* /*
char acce[128], inte[128]; char acce[128], inte[128];
@ -559,10 +558,7 @@ int RemobAction(SConnection *pCon, SicsInterp *pSics, void *pData,
snprintf(inte, sizeof(inte), "!%s.interruptmode", remob->name); snprintf(inte, sizeof(inte), "!%s.interruptmode", remob->name);
*/ */
argv0 = argv[0]; cmd = Arg2Tcl0(argc-1, argv+1, buf, sizeof buf, remob->name);
argv[0] = remob->name;
cmd = Arg2Tcl(argc, argv, buf, sizeof buf);
argv[0] = argv0;
if (cmd) { if (cmd) {
RemTransact(remserver, rc, pCon, cmd, ">", NULL); RemTransact(remserver, rc, pCon, cmd, ">", NULL);
if (cmd != buf) free(cmd); if (cmd != buf) free(cmd);

View File

@ -400,12 +400,18 @@ typedef enum _CharType {eSpace, eNum,eeText,eQuote} CharType;
return 1; return 1;
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
char *Arg2Tcl(int argc, char *argv[], char *buffer, int buffersize) { char *Arg2Tcl0(int argc, char *argv[], char *buffer, int buffersize, char *prepend) {
int i, l, firstArgToQuote, quote; int i, l, firstArgToQuote, quote, prependlen;
char ch; char ch;
char *res, *arg; char *res, *arg;
l = 0; if (prepend) {
prependlen = strlen(prepend);
l = prependlen + 1;
} else {
prependlen = 0;
l = 0;
}
firstArgToQuote = argc; firstArgToQuote = argc;
quote = 0; quote = 0;
for (i=0; i<argc; i++) { for (i=0; i<argc; i++) {
@ -443,6 +449,11 @@ char *Arg2Tcl(int argc, char *argv[], char *buffer, int buffersize) {
if (buffer == NULL) return NULL; if (buffer == NULL) return NULL;
} }
res = buffer; res = buffer;
if (prepend) {
strcpy(res, prepend);
res += prependlen;
*res++ = ' ';
}
for (i=0; i<argc; i++) { for (i=0; i<argc; i++) {
if (i >= firstArgToQuote) *res++ = '"'; if (i >= firstArgToQuote) *res++ = '"';
arg = argv[i]; arg = argv[i];
@ -464,6 +475,10 @@ char *Arg2Tcl(int argc, char *argv[], char *buffer, int buffersize) {
*res='\0'; *res='\0';
return buffer; return buffer;
} }
/*--------------------------------------------------------------------------*/
char *Arg2Tcl(int argc, char *argv[], char *buffer, int buffersize) {
return Arg2Tcl0(argc, argv, buffer, buffersize, NULL);
}
/*============================================================================ /*============================================================================
Testprogram, can be activated by defining MAIN Testprogram, can be activated by defining MAIN

View File

@ -87,5 +87,10 @@ typedef struct _TokenEntry {
if (result != NULL && result != buffer) free(result); if (result != NULL && result != buffer) free(result);
!*/ !*/
char *Arg2Tcl0(int argc, char *argv[], char *buffer, int buffersize, char *prepend);
/*!
This function is added for convenience, and acts similar to Arg2Tcl.
If prepend is not NULL, its contents appear untreated before the args.
A space is used as separator.
!*/
#endif #endif

149
statistics.c Normal file
View File

@ -0,0 +1,149 @@
#include <sics.h>
#include <sys/time.h>
#include "statistics.h"
typedef struct timeval tv_t;
struct Statistics {
tv_t tim;
long cnt;
char *name;
Statistics *next;
};
static Statistics *current;
static tv_t last, lastStat;
static Statistics *idle = NULL, *list;
static int init = 1;
/*-----------------------------------------------------------------------*/
tv_t timeDif(tv_t t1, tv_t t2) {
tv_t result;
result.tv_usec = t2.tv_usec - t1.tv_usec;
result.tv_sec = t2.tv_sec - t1.tv_sec;
if (result.tv_usec < 0) {
result.tv_usec += 1000000;
result.tv_sec --;
}
if (result.tv_sec < 0) {
result.tv_sec += 24*3600;
}
return result;
}
/*-----------------------------------------------------------------------*/
void timeAdd(tv_t *t1, tv_t t2) {
t1->tv_usec += t2.tv_usec;
t1->tv_sec += t2.tv_sec + (t1->tv_usec / 1000000);
t1->tv_usec %= 1000000;
}
/*-----------------------------------------------------------------------*/
double timeFloat(tv_t t) {
return t.tv_sec + 1e-6 * t.tv_usec;
}
/*-----------------------------------------------------------------------*/
int StatisticsCommand(SConnection *con, SicsInterp *pSics, void *pData,
int argc, char *argv[]) {
Statistics *p;
tv_t now;
double dif, percent, dt;
gettimeofday(&now, 0);
dif = timeFloat(timeDif(lastStat, now));
SCPrintf(con, eStatus, " calls time[%] mean[ms] command");
SCPrintf(con, eStatus, "--------------------------------------");
for (p = list; p != NULL; p = p->next) {
if (dif > 0) {
percent = timeFloat(p->tim) * 100 / dif;
if (percent > 0) {
if (p->cnt > 0) {
dt = timeFloat(p->tim) * 1000.0 / p->cnt;
} else {
dt = 0;
}
SCPrintf(con, eStatus, "%7ld %7.1f %8.2f %s", p->cnt,
percent, dt, p->name);
}
}
p->cnt = 0;
p->tim.tv_sec = 0;
p->tim.tv_usec = 0;
}
lastStat = now;
return 1;
}
/*-----------------------------------------------------------------------*/
Statistics *StatisticsNew(char *name) {
Statistics *new;
if (init) {
gettimeofday(&lastStat, 0);
last = lastStat;
init = 0;
}
new = calloc(1,sizeof(*new));
if (new) {
new->cnt = 0;
new->tim.tv_sec = 0;
new->tim.tv_usec = 0;
new->next = list;
new->name = strdup(name);
list = new;
}
return new;
}
/*-----------------------------------------------------------------------*/
void StatisticsKill(Statistics *stat) {
Statistics *p, **last;
/* find in list */
last = &list;
for (p = list; p != NULL && p != stat; p = p->next) {
last = &p->next;
}
/* remove from list */
if (p == stat) {
*last = p->next;
}
/* kill it */
if (stat->name) {
free(stat->name);
stat->name = NULL;
}
free(stat);
}
/*-----------------------------------------------------------------------*/
static void StatisticsSet(Statistics *stat) {
tv_t now;
if (stat != NULL) {
gettimeofday(&now, 0);
timeAdd(&current->tim, timeDif(last, now));
last = now;
}
current = stat;
}
/*-----------------------------------------------------------------------*/
Statistics *StatisticsBegin(Statistics *stat) {
Statistics *res;
res = current;
StatisticsSet(stat);
current->cnt ++;
return res;
}
/*-----------------------------------------------------------------------*/
void StatisticsEnd(Statistics *stat) {
StatisticsSet(stat);
}
/*-----------------------------------------------------------------------*/
void StatisticsInit(void) {
if (idle == NULL) {
AddCmd("statistics", StatisticsCommand);
last = lastStat;
idle = StatisticsNew("<idle>");
current = idle;
}
}

36
statistics.h Normal file
View File

@ -0,0 +1,36 @@
/* statistics.h
statistics on the time spend for commands/tasks
*/
#ifndef STATISTICS_H
#define STATISTICS_H
typedef struct Statistics Statistics;
Statistics *StatisticsNew(char *name);
/* create a new Statistics item */
void StatisticsKill(Statistics *s);
/* kill item */
Statistics *StatisticsBegin(Statistics *s);
void StatisticsEnd(Statistics *s);
/*
Switch statistics to item s.
Typical usage:
old = StatisticsBegin(thisItem);
...do work related with thisItem...
StatisticsEnd(old);
*/
void StatisticsInit(void);
/* initialize module and activate statistics command */
#endif

View File

@ -162,7 +162,7 @@ int TclIntAction(SConnection *pCon, SicsInterp *pSics, void *pData,
cmd = Arg2Tcl(argc-2, &argv[2],pBuffer,1023); cmd = Arg2Tcl(argc-2, &argv[2],pBuffer,1023);
if (cmd) { if (cmd) {
if(self->fd != NULL){ if(self->fd != NULL){
fprintf(self->fd,"%s\n",pBuffer); fprintf(self->fd,"%s\n",cmd);
} }
if (cmd != pBuffer) free(cmd); if (cmd != pBuffer) free(cmd);
SCSendOK(pCon); SCSendOK(pCon);

View File

@ -120,6 +120,7 @@
{ {
deleteCircular(self->pTail); deleteCircular(self->pTail);
} }
free(self); /* M.Z. */
return 1; return 1;
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/