/* Scan task, one per PLC */ static void PLC_scan_task(PLC *plc) { ScanList *list; epicsTimeStamp next_schedule, start_time, end_time; double timeout, delay, quantum; eip_bool transfer_ok, reset_next_schedule; quantum = epicsThreadSleepQuantum(); timeout = (double)ETHERIP_TIMEOUT/1000.0; scan_loop: /* --------- The Scan Loop for one PLC -------- */ if (epicsMutexLock(plc->lock) != epicsMutexLockOK) { EIP_printf_time(1, "drvEtherIP scan task for PLC '%s'" " cannot take plc->lock\n", plc->name); return; } if (!assert_PLC_connect(plc)) { /* don't rush since connection takes network bandwidth */ epicsMutexUnlock(plc->lock); EIP_printf_time(2, "drvEtherIP: PLC '%s' is disconnected\n", plc->name); epicsThreadSleep(timeout); goto scan_loop; } EIP_printf_time(10, "drvEtherIP scan PLC '%s'\n", plc->name); reset_next_schedule = true; epicsTimeGetCurrent(&start_time); for (list = DLL_first(ScanList,&plc->scanlists); list; list = DLL_next(ScanList,list)) { if (! list->enabled) continue; if (epicsTimeLessThanEqual(&list->scheduled_time, &start_time)) { epicsTimeGetCurrent(&list->scan_time); transfer_ok = process_ScanList(plc->connection, list); epicsTimeGetCurrent(&end_time); list->last_scan_time = epicsTimeDiffInSeconds(&end_time, &list->scan_time); /* update statistics */ if (list->last_scan_time > list->max_scan_time) list->max_scan_time = list->last_scan_time; if (list->last_scan_time < list->min_scan_time || list->min_scan_time == 0.0) list->min_scan_time = list->last_scan_time; if (transfer_ok) /* re-schedule exactly */ { list->scheduled_time = list->scan_time; epicsTimeAddSeconds(&list->scheduled_time, list->period); } else { /* end_time+fixed delay, ignore extra due to error */ list->scheduled_time = end_time; epicsTimeAddSeconds(&list->scheduled_time, timeout); ++list->list_errors; ++plc->plc_errors; disconnect_PLC(plc); epicsMutexUnlock(plc->lock); goto scan_loop; } } /* Update time for list that's due next */ if (reset_next_schedule || epicsTimeLessThan(&list->scheduled_time, &next_schedule)) { reset_next_schedule = false; next_schedule = list->scheduled_time; } } epicsMutexUnlock(plc->lock); /* fallback for empty/degenerate scan list */ if (reset_next_schedule) delay = EIP_MIN_TIMEOUT; else { epicsTimeGetCurrent(&start_time); delay = epicsTimeDiffInSeconds(&next_schedule, &start_time); if (delay > 60.0) { char tsString[50]; printf("Scanlist %g secs has scheduling problem, delay = %g sec\n", list->period, delay); epicsTimeToStrftime(tsString, sizeof(tsString), "%Y/%m/%d %H:%M:%S.%04f", &list->scan_time); printf(" 'Scan time' : %s\n", tsString); epicsTimeToStrftime(tsString, sizeof(tsString), "%Y/%m/%d %H:%M:%S.%04f", &start_time); printf(" 'Current time' : %s\n", tsString); epicsTimeToStrftime(tsString, sizeof(tsString), "%Y/%m/%d %H:%M:%S.%04f", &next_schedule); printf(" 'Next time' : %s\n", tsString); /* Attempt to hack around this by waiting a minute, * hoping that the clock looks better by then. * Also resetting the scheduled time to 'now'. */ delay = 60.0; list->scheduled_time = start_time; ++list->sched_errors; } } /* Sleep until next turn. */ if (delay > 0.0) epicsThreadSleep(delay); else if (delay <= -quantum) { EIP_printf(8, "drvEtherIP scan task slow, %g sec delay\n", delay); ++plc->slow_scans; /* hmm, "plc" not locked... */ } goto scan_loop; }
static void periodicTask(void *arg) { periodic_scan_list *ppsl = (periodic_scan_list *)arg; epicsTimeStamp next, reported; unsigned int overruns = 0; double report_delay = OVERRUN_REPORT_DELAY; double overtime = 0.0; double over_min = 0.0; double over_max = 0.0; const double penalty = (ppsl->period >= 2) ? 1 : (ppsl->period / 2); taskwdInsert(0, NULL, NULL); epicsEventSignal(startStopEvent); epicsTimeGetCurrent(&next); reported = next; while (ppsl->scanCtl != ctlExit) { double delay; epicsTimeStamp now; if (ppsl->scanCtl == ctlRun) scanList(&ppsl->scan_list); epicsTimeAddSeconds(&next, ppsl->period); epicsTimeGetCurrent(&now); delay = epicsTimeDiffInSeconds(&next, &now); if (delay <= 0.0) { if (overtime == 0.0) { overtime = over_min = over_max = -delay; } else { overtime -= delay; if (over_min + delay > 0) over_min = -delay; if (over_max + delay < 0) over_max = -delay; } delay = penalty; ppsl->overruns++; next = now; epicsTimeAddSeconds(&next, delay); if (++overruns >= 10 && epicsTimeDiffInSeconds(&now, &reported) > report_delay) { errlogPrintf("\ndbScan warning from '%s' scan thread:\n" "\tScan processing averages %.2f seconds (%.2f .. %.2f).\n" "\tOver-runs have now happened %u times in a row.\n" "\tTo fix this, move some records to a slower scan rate.\n", ppsl->name, ppsl->period + overtime / overruns, ppsl->period + over_min, ppsl->period + over_max, overruns); reported = now; if (report_delay < (OVERRUN_REPORT_MAX / 2)) report_delay *= 2; else report_delay = OVERRUN_REPORT_MAX; } } else { overruns = 0; report_delay = OVERRUN_REPORT_DELAY; overtime = 0.0; } epicsEventWaitWithTimeout(ppsl->loopEvent, delay); } taskwdRemove(0); epicsEventSignal(startStopEvent); }