Merge from parallel-cbthreads-2: return value for callbackRequest, add tests

This commit is contained in:
Ralph Lange
2014-08-25 14:40:37 -07:00
parent ca8eda8090
commit 15415b5590
10 changed files with 594 additions and 15 deletions
+14 -7
View File
@@ -146,7 +146,12 @@ static void callbackTask(void *arg)
while(TRUE) {
void *ptr;
epicsEventMustWait(callbackSem[priority]);
while((ptr = epicsRingPointerPop(callbackQ[priority]))) {
while ((ptr = epicsRingPointerPop(callbackQ[priority]))) {
/* Retrigger if there are more threads and more work */
if (callbackThreadsRunning[priority] > 1
&& !epicsRingPointerIsEmpty(callbackQ[priority])) {
epicsEventTrigger(callbackSem[priority]);
}
CALLBACK *pcallback = (CALLBACK *)ptr;
if (ptr == &exitCallback) goto shutdown;
ringOverflow[priority] = FALSE;
@@ -220,21 +225,21 @@ void callbackInit(void)
}
/* This routine can be called from interrupt context */
void callbackRequest(CALLBACK *pcallback)
int callbackRequest(CALLBACK *pcallback)
{
int priority;
int pushOK;
if (!pcallback) {
epicsInterruptContextMessage("callbackRequest: pcallback was NULL\n");
return;
return S_db_notInit;
}
priority = pcallback->priority;
if (priority < 0 || priority >= NUM_CALLBACK_PRIORITIES) {
epicsInterruptContextMessage("callbackRequest: Bad priority\n");
return;
return S_db_badChoice;
}
if (ringOverflow[priority]) return;
if (ringOverflow[priority]) return S_db_bufFull;
pushOK = epicsRingPointerPush(callbackQ[priority], pcallback);
@@ -245,8 +250,10 @@ void callbackRequest(CALLBACK *pcallback)
strcat(msg, " ring buffer full\n");
epicsInterruptContextMessage(msg);
ringOverflow[priority] = TRUE;
return S_db_bufFull;
}
epicsEventSignal(callbackSem[priority]);
return 0;
}
static void ProcessCallback(CALLBACK *pcallback)
@@ -267,11 +274,11 @@ void callbackSetProcess(CALLBACK *pcallback, int Priority, void *pRec)
callbackSetUser(pRec, pcallback);
}
void callbackRequestProcessCallback(CALLBACK *pcallback,
int callbackRequestProcessCallback(CALLBACK *pcallback,
int Priority, void *pRec)
{
callbackSetProcess(pcallback, Priority, pRec);
callbackRequest(pcallback);
return callbackRequest(pcallback);
}
static void notify(void *pPrivate)
+2 -2
View File
@@ -57,11 +57,11 @@ typedef void (*CALLBACKFUNC)(struct callbackPvt*);
( (USER) = (void *)((CALLBACK *)(PCALLBACK))->user )
epicsShareFunc void callbackInit(void);
epicsShareFunc void callbackRequest(CALLBACK *pCallback);
epicsShareFunc void callbackShutdown(void);
epicsShareFunc int callbackRequest(CALLBACK *pCallback);
epicsShareFunc void callbackSetProcess(
CALLBACK *pcallback, int Priority, void *pRec);
epicsShareFunc void callbackRequestProcessCallback(
epicsShareFunc int callbackRequestProcessCallback(
CALLBACK *pCallback,int Priority, void *pRec);
epicsShareFunc void callbackRequestDelayed(
CALLBACK *pCallback,double seconds);
+2
View File
@@ -199,6 +199,8 @@ struct dbr_alDouble {DBRalDouble};
#define S_db_cntSpwn (M_dbAccess|63) /*Cannot spawn dbContTask*/
#define S_db_cntCont (M_dbAccess|65) /*Cannot resume dbContTask*/
#define S_db_noMemory (M_dbAccess|66) /*unable to allocate data structure from pool*/
#define S_db_notInit (M_dbAccess|67) /*Not initialized*/
#define S_db_bufFull (M_dbAccess|68) /*Buffer full*/
epicsShareFunc long dbPutSpecial(struct dbAddr *paddr,int pass);
epicsShareFunc struct rset * dbGetRset(const struct dbAddr *paddr);
+25 -5
View File
@@ -122,6 +122,8 @@ typedef struct io_scan_list {
CALLBACK callback;
scan_list scan_list;
struct io_scan_list *next;
io_scan_complete cb;
void * arg;
} io_scan_list;
static io_scan_list *iosl_head[NUM_CALLBACK_PRIORITIES] = {
@@ -507,19 +509,31 @@ void scanIoInit(IOSCANPVT *ppioscanpvt)
}
}
void scanIoRequest(IOSCANPVT pioscanpvt)
/* return a bit mask indicating each prioity level
* in which a callback request was queued.
*/
unsigned int scanIoRequest(IOSCANPVT pioscanpvt)
{
int prio;
unsigned int queued = 0;
if (scanCtl != ctlRun) return;
if (scanCtl != ctlRun) return 0;
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
io_scan_list *piosl = &pioscanpvt[prio];
if (ellCount(&piosl->scan_list.list) > 0)
callbackRequest(&piosl->callback);
if(!callbackRequest(&piosl->callback))
queued |= 1<<prio;
}
return queued;
}
/* May not be called while a scan request is queued or running */
void scanIoSetComplete(IOSCANPVT pioscanpvt, io_scan_complete cb, void* arg)
{
pioscanpvt->cb = cb;
pioscanpvt->arg = arg;
}
void scanOnce(struct dbCommon *precord)
{
static int newOverflow = TRUE;
@@ -747,9 +761,15 @@ static void spawnPeriodic(int ind)
static void ioeventCallback(CALLBACK *pcallback)
{
io_scan_list *piosl;
io_scan_list *pioslLow;
callbackGetUser(piosl, pcallback);
scanList(&piosl->scan_list);
pioslLow = piosl - pcallback->priority;
if(pioslLow->cb)
(*pioslLow->cb)(pioslLow->arg,
pioslLow,
pcallback->priority);
}
static void printList(scan_list *psl, char *message)
+4 -1
View File
@@ -39,6 +39,8 @@ struct io_scan_list;
typedef struct io_scan_list *IOSCANPVT;
typedef struct event_list *EVENTPVT;
typedef void (*io_scan_complete)(void *, IOSCANPVT, int);
struct dbCommon;
epicsShareFunc long scanInit(void);
@@ -65,7 +67,8 @@ epicsShareFunc int scanpel(const char *event_name);
epicsShareFunc int scanpiol(void);
epicsShareFunc void scanIoInit(IOSCANPVT *);
epicsShareFunc void scanIoRequest(IOSCANPVT);
epicsShareFunc unsigned int scanIoRequest(IOSCANPVT);
epicsShareFunc void scanIoSetComplete(IOSCANPVT, io_scan_complete, void*);
#ifdef __cplusplus
}
+13
View File
@@ -65,6 +65,18 @@ dbStateTest_SRCS += dbStateTest.c
testHarness_SRCS += dbStateTest.c
TESTS += dbStateTest
TARGETS += $(COMMON_DIR)/scanIoTest.dbd
scanIoTest_DBD += menuGlobal.dbd
scanIoTest_DBD += menuConvert.dbd
scanIoTest_DBD += yRecord.dbd
TESTPROD_HOST += scanIoTest
scanIoTest_SRCS += scanIoTest.c
scanIoTest_SRCS += scanIoTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += scanIoTest.c
testHarness_SRCS += scanIoTest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/scanIoTest.dbd ../scanIoTest.db
TESTS += scanIoTest
TESTPROD_HOST += dbChannelTest
dbChannelTest_SRCS += dbChannelTest.c
dbChannelTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
@@ -109,3 +121,4 @@ TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES
xRecord$(DEP): $(COMMON_DIR)/xRecord.h
scanIoTest$(DEP): $(COMMON_DIR)/yRecord.h
+1
View File
@@ -35,6 +35,7 @@ void epicsRunDbTests(void)
runTest(callbackTest);
runTest(dbStateTest);
runTest(dbShutdownTest);
runTest(scanIoTest);
runTest(dbLockTest);
runTest(dbPutLinkTest);
runTest(testDbChannel);
+519
View File
@@ -0,0 +1,519 @@
/*************************************************************************\
* Copyright (c) 2013 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2013 Helmholtz-Zentrum Berlin
* für Materialien und Energie GmbH.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Ralph Lange <Ralph.Lange@gmx.de>
*/
#include <stdlib.h>
#include "epicsEvent.h"
#include "epicsMessageQueue.h"
#include "epicsPrint.h"
#include "epicsMath.h"
#include "alarm.h"
#include "menuPriority.h"
#include "dbChannel.h"
#include "dbStaticLib.h"
#include "dbAccessDefs.h"
#include "dbScan.h"
#include "dbLock.h"
#include "dbUnitTest.h"
#include "dbCommon.h"
#include "registry.h"
#include "registryRecordType.h"
#include "registryDeviceSupport.h"
#include "recSup.h"
#include "devSup.h"
#include "iocInit.h"
#include "callback.h"
#include "ellLib.h"
#include "epicsUnitTest.h"
#include "testMain.h"
#include "osiFileName.h"
#define GEN_SIZE_OFFSET
#include "yRecord.h"
#include "epicsExport.h"
#define ONE_THREAD_LOOPS 101
#define PAR_THREAD_LOOPS 53
#define CB_THREAD_LOOPS 13
#define NO_OF_THREADS 7
#define NO_OF_MEMBERS 5
#define NO_OF_GROUPS 11
#define NO_OF_MID_THREADS 3
static int noOfGroups = NO_OF_GROUPS;
static int noOfIoscans = NO_OF_GROUPS;
static IOSCANPVT *ioscanpvt; /* Soft interrupt sources */
static ELLLIST *pvtList; /* Per group private part lists */
static int executionOrder;
static int orderFail;
static int testNo;
static epicsMessageQueueId *mq; /* Per group message queue */
static epicsEventId *barrier; /* Per group barrier event */
static int *cbCounter;
struct pvtY {
ELLNODE node;
yRecord *prec;
int group;
int member;
int count;
int processed;
int callback;
};
/* test2: priority and ioscan index for each group
* used priorities are expressed in the bit pattern of (ioscan index + 1) */
struct groupItem {
int prio;
int ioscan;
} groupTable[12] = {
{ 0, 0 },
{ 1, 1 },
{ 0, 2 }, { 1, 2 },
{ 2, 3 },
{ 0, 4 }, { 2, 4 },
{ 1, 5 }, { 2, 5 },
{ 0, 6 }, { 1, 6 }, { 2, 6 }
};
static int recsProcessed = 1;
static int noDoubleCallback = 1;
void scanIoTest_registerRecordDeviceDriver(struct dbBase *);
long count_bits(long n) {
unsigned int c; // c accumulates the total bits set in v
for (c = 0; n; c++)
n &= n - 1; // clear the least significant bit set
return c;
}
/*************************************************************************\
* yRecord: minimal record needed to test I/O Intr scanning
\*************************************************************************/
static long get_ioint_info(int cmd, yRecord *prec, IOSCANPVT *ppvt)
{
struct pvtY *pvt = (struct pvtY *)(prec->dpvt);
if (testNo == 2)
*ppvt = ioscanpvt[groupTable[pvt->group].ioscan];
else
*ppvt = ioscanpvt[pvt->group];
return 0;
}
struct ydset {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN process;
} devY = {
5,
NULL,
NULL,
NULL,
get_ioint_info,
NULL
};
epicsExportAddress(dset, devY);
static long init_record(yRecord *prec, int pass)
{
struct pvtY *pvt;
if (pass == 0) return 0;
pvt = (struct pvtY *) calloc(1, sizeof(struct pvtY));
prec->dpvt = pvt;
pvt->prec = prec;
sscanf(prec->name, "g%dm%d", &pvt->group, &pvt->member);
ellAdd(&pvtList[pvt->group], &pvt->node);
return 0;
}
static long process(yRecord *prec)
{
struct pvtY *pvt = (struct pvtY *)(prec->dpvt);
if (testNo == 0) {
// Single callback thread
if (executionOrder != pvt->member) {
orderFail = 1;
}
pvt->count++;
if (++executionOrder == NO_OF_MEMBERS) executionOrder = 0;
} else {
pvt->count++;
if (pvt->member == 0) {
epicsMessageQueueSend(mq[pvt->group], NULL, 0);
epicsEventMustWait(barrier[pvt->group]);
}
}
pvt->processed = 1;
return 0;
}
rset yRSET={
4,
NULL, //report,
NULL, //initialize,
init_record,
process
};
epicsExportAddress(rset, yRSET);
static void startMockIoc(void) {
char substitutions[256];
int i, j;
char *prio[] = { "LOW", "MEDIUM", "HIGH" };
if (testNo == 2) {
noOfGroups = 12;
noOfIoscans = 7;
}
ioscanpvt = calloc(noOfIoscans, sizeof(IOSCANPVT));
mq = calloc(noOfGroups, sizeof(epicsMessageQueueId));
barrier = calloc(noOfGroups, sizeof(epicsEventId));
pvtList = calloc(noOfGroups, sizeof(ELLLIST));
cbCounter = calloc(noOfGroups, sizeof(int));
if (dbReadDatabase(&pdbbase, "scanIoTest.dbd",
"." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
"../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
testAbort("Error reading database description 'scanIoTest.dbd'");
callbackParallelThreads(1, "Low");
callbackParallelThreads(NO_OF_MID_THREADS, "Medium");
callbackParallelThreads(NO_OF_THREADS, "High");
for (i = 0; i < noOfIoscans; i++) {
scanIoInit(&ioscanpvt[i]);
}
for (i = 0; i < noOfGroups; i++) {
mq[i] = epicsMessageQueueCreate(NO_OF_MEMBERS, 1);
barrier[i] = epicsEventMustCreate(epicsEventEmpty);
ellInit(&pvtList[i]);
}
scanIoTest_registerRecordDeviceDriver(pdbbase);
for (i = 0; i < noOfGroups; i++) {
for (j = 0; j < NO_OF_MEMBERS; j++) {
sprintf(substitutions, "GROUP=%d,MEMBER=%d,PRIO=%s", i, j,
testNo==0?"LOW":(testNo==1?"HIGH":prio[groupTable[i].prio]));
if (dbReadDatabase(&pdbbase, "scanIoTest.db",
"." OSI_PATH_LIST_SEPARATOR "..", substitutions))
testAbort("Error reading test database 'scanIoTest.db'");
}
}
testIocInitOk();
}
static void stopMockIoc(void) {
int i;
testIocShutdownOk();
epicsThreadSleep(0.1);
for (i = 0; i < noOfGroups; i++) {
epicsMessageQueueDestroy(mq[i]); mq[i] = NULL;
epicsEventDestroy(barrier[i]); barrier[i] = NULL;
ellFree(&pvtList[i]);
}
free(mq);
free(barrier);
free(pvtList);
free(cbCounter);
testdbCleanup();
}
static void checkProcessed(void *user, IOSCANPVT ioscan, int prio) {
struct pvtY *pvt;
int group = -1;
int i;
for (i = 0; i < noOfGroups; i++) {
if (ioscanpvt[groupTable[i].ioscan] == ioscan
&& groupTable[i].prio == prio) {
group = i;
break;
}
}
if (group == -1)
testAbort("invalid ioscanpvt in scanio callback");
cbCounter[group]++;
for (pvt = (struct pvtY *)ellFirst(&pvtList[group]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->callback == 1) {
testDiag("callback for rec %s arrived twice\n", pvt->prec->name);
noDoubleCallback = 0;
}
if (pvt->processed == 0) {
testDiag("rec %s was not processed\n", pvt->prec->name);
recsProcessed = 0;
}
pvt->callback = 1;
}
}
/*************************************************************************\
* scanIoTest: Test I/O Intr scanning
* including parallel callback threads and scanio callbacks
\*************************************************************************/
MAIN(scanIoTest)
{
int i, j;
int loop;
int max_one, max_one_all;
int parallel, parallel_all;
int result;
int cbCountOk;
long waiting;
struct pvtY *pvt;
testPlan(10);
if (noOfGroups < NO_OF_THREADS)
testAbort("ERROR: This test requires number of ioscan sources >= number of parallel threads");
/**************************************\
* Single callback thread
\**************************************/
testNo = 0;
startMockIoc();
testDiag("Testing single callback thread");
testDiag(" using %d ioscan sources, %d records for each, and %d loops",
noOfGroups, NO_OF_MEMBERS, ONE_THREAD_LOOPS);
for (j = 0; j < ONE_THREAD_LOOPS; j++) {
for (i = 0; i < noOfIoscans; i++) {
scanIoRequest(ioscanpvt[i]);
}
}
epicsThreadSleep(0.1);
testOk((orderFail==0), "No out-of-order processing");
result = 1;
for (i = 0; i < noOfGroups; i++) {
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->count != ONE_THREAD_LOOPS) result = 0;
}
}
testOk(result, "All per-record process counters match number of loops");
stopMockIoc();
/**************************************\
* Multiple parallel callback threads
\**************************************/
testNo = 1;
startMockIoc();
testDiag("Testing multiple parallel callback threads");
testDiag(" using %d ioscan sources, %d records for each, %d loops, and %d parallel threads",
noOfIoscans, NO_OF_MEMBERS, PAR_THREAD_LOOPS, NO_OF_THREADS);
for (j = 0; j < PAR_THREAD_LOOPS; j++) {
for (i = 0; i < noOfIoscans; i++) {
scanIoRequest(ioscanpvt[i]);
}
}
/* With parallel cb threads, order and distribution to threads are not guaranteed.
* We have stop barrier events for each request (in the first record).
* Test schedule:
* - After the requests have been put in the queue, NO_OF_THREADS threads should have taken
* one request each.
* - Each barrier event is given PAR_THREAD_LOOPS times.
* - Whenever things stop, there should be four threads waiting, one request each.
* - After all loops, each record should have processed PAR_THREAD_LOOPS times.
*/
max_one_all = 1;
parallel_all = 1;
for (loop = 0; loop < (PAR_THREAD_LOOPS * noOfGroups) / NO_OF_THREADS + 1; loop++) {
max_one = 1;
parallel = 0;
waiting = 0;
j = 0;
do {
epicsThreadSleep(0.001);
j++;
for (i = 0; i < noOfGroups; i++) {
int l = epicsMessageQueuePending(mq[i]);
while (epicsMessageQueueTryReceive(mq[i], NULL, 0) != -1);
if (l == 1) {
waiting |= 1 << i;
} else if (l > 1) {
max_one = 0;
}
}
parallel = count_bits(waiting);
} while (j < 5 && parallel < NO_OF_THREADS);
if (!max_one) max_one_all = 0;
if (loop < (PAR_THREAD_LOOPS * noOfGroups) / NO_OF_THREADS) {
if (!(parallel == NO_OF_THREADS)) parallel_all = 0;
} else {
/* In the last run of the loop only the remaining requests are processed */
if (!(parallel == PAR_THREAD_LOOPS * noOfGroups % NO_OF_THREADS)) parallel_all = 0;
}
for (i = 0; i < noOfGroups; i++) {
if (waiting & (1 << i)) {
epicsEventTrigger(barrier[i]);
}
}
}
testOk(max_one_all, "No thread took more than one request per loop");
testOk(parallel_all, "Correct number of requests were being processed in parallel in each loop");
epicsThreadSleep(0.1);
result = 1;
for (i = 0; i < noOfGroups; i++) {
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->count != PAR_THREAD_LOOPS) {
testDiag("Process counter for record %s (%d) does not match loop count (%d)",
pvt->prec->name, pvt->count, PAR_THREAD_LOOPS);
result = 0;
}
}
}
testOk(result, "All per-record process counters match number of loops");
stopMockIoc();
/**************************************\
* Scanio callback mechanism
\**************************************/
testNo = 2;
startMockIoc();
for (i = 0; i < noOfIoscans; i++) {
scanIoSetComplete(ioscanpvt[i], checkProcessed, NULL);
}
testDiag("Testing scanio callback mechanism");
testDiag(" using %d ioscan sources, %d records for each, %d loops, and 1 LOW / %d MEDIUM / %d HIGH parallel threads",
noOfIoscans, NO_OF_MEMBERS, CB_THREAD_LOOPS, NO_OF_MID_THREADS, NO_OF_THREADS);
result = 1;
for (j = 0; j < CB_THREAD_LOOPS; j++) {
for (i = 0; i < noOfIoscans; i++) {
int prio_used;
prio_used = scanIoRequest(ioscanpvt[i]);
if (i+1 != prio_used)
result = 0;
}
}
testOk(result, "All requests return the correct priority callback mask (all 7 permutations covered)");
/* Test schedule:
* After the requests have been put in the queue, it is checked
* - that each callback arrives exactly once,
* - after all records in the group have been processed.
*/
/* loop count times 4 since (worst case) one loop triggers 4 groups for the single LOW thread */
for (loop = 0; loop < CB_THREAD_LOOPS * 4; loop++) {
max_one = 1;
parallel = 0;
waiting = 0;
j = 0;
do {
epicsThreadSleep(0.001);
j++;
for (i = 0; i < noOfGroups; i++) {
int l = epicsMessageQueuePending(mq[i]);
while (epicsMessageQueueTryReceive(mq[i], NULL, 0) != -1);
if (l == 1) {
waiting |= 1 << i;
} else if (l > 1) {
max_one = 0;
}
}
parallel = count_bits(waiting);
} while (j < 5);
\
for (i = 0; i < noOfGroups; i++) {
if (waiting & (1 << i)) {
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
pvt->processed = 0;
pvt->callback = 0;
/* record processing will set this at the end of process() */
}
epicsEventTrigger(barrier[i]);
}
}
}
epicsThreadSleep(0.1);
testOk(recsProcessed, "Each callback occured after all records in the group were processed");
testOk(noDoubleCallback, "No double callbacks occured in any loop");
result = 1;
cbCountOk = 1;
for (i = 0; i < noOfGroups; i++) {
if (cbCounter[i] != CB_THREAD_LOOPS) {
testDiag("Callback counter for group %d (%d) does not match loop count (%d)",
i, cbCounter[i], CB_THREAD_LOOPS);
cbCountOk = 0;
}
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->count != CB_THREAD_LOOPS) {
testDiag("Process counter for record %s (%d) does not match loop count (%d)",
pvt->prec->name, pvt->count, CB_THREAD_LOOPS);
result = 0;
}
}
}
testOk(result, "All per-record process counters match number of loops");
testOk(cbCountOk, "All per-group callback counters match number of loops");
stopMockIoc();
return testDone();
}
+4
View File
@@ -0,0 +1,4 @@
record(y, g$(GROUP)m$(MEMBER)) {
field(SCAN, "I/O Intr")
field(PRIO, "$(PRIO)")
}
+10
View File
@@ -0,0 +1,10 @@
# This is a minimal I/O scanned record
recordtype(y) {
include "dbCommon.dbd"
field(VAL, DBF_LONG) {
prompt("Value")
}
}
device(y,CONSTANT,devY,"ScanIO Test")