void ProcessVariable::unsubscribe(Guard &guard) { guard.check(__FILE__, __LINE__, mutex); // See comments in stop(): this->id is already 0, state==INIT. if (isSubscribed(guard)) { #ifdef CHECK_EVID void *user = peek_evid_userptr(ev_id); LOG_ASSERT(user == this); #endif evid _ev_id = ev_id; ev_id = 0; GuardRelease release(__FILE__, __LINE__, guard); { Guard ctx_guard(__FILE__, __LINE__, ctx); LOG_ASSERT(ctx.isAttached(ctx_guard)); } try { ca_clear_subscription(_ev_id); } catch (std::exception &e) { LOG_MSG("ProcessVariable::unsubscribe(%s): %s\n", getName().c_str(), e.what()); } catch (...) { LOG_MSG("ProcessVariable::unsubscribe(%s): Unknown Exception\n", getName().c_str()); } } }
void ProcessVariable::getValue(Guard &guard) { guard.check(__FILE__, __LINE__, mutex); if (state != CONNECTED) return; // Can't get ++outstanding_gets; chid _id = id; GuardRelease release(__FILE__, __LINE__, guard); // Unlock while in CAC. { int status; try { status = ca_array_get_callback(dbr_type, dbr_count, _id, value_callback, this); } catch (std::exception &e) { LOG_MSG("ProcessVariable::getValue(%s): %s\n", getName().c_str(), e.what()); } catch (...) { LOG_MSG("ProcessVariable::getValue(%s): Unknown Exception\n", getName().c_str()); } if (status != ECA_NORMAL) { LOG_MSG("%s: ca_array_get_callback failed: %s\n", getName().c_str(), ca_message(status)); return; } Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.requestFlush(ctx_guard); } }
TEST_CASE test_sample_mechanism() { EngineConfig config; ProcessVariableContext ctx; COMMENT("Testing the basic SampleMechanism..."); // The data pipe goes from the sample's PV // to the DisableFilter in sample and then // on to sample. We needn't insert a filter, // but gcc 3.2.3 complains about unknown 'sample' in // SampleMechanism sample(config, ctx, "janet", 5.0, &sample); // so we do use an in-between filter. DemoProcessVariableListener filter; SampleMechanism sample(config, ctx, "janet", 5.0, &filter); filter.setListener(&sample); TEST(sample.getName() == "janet"); COMMENT("Trying to connect..."); { Guard guard(__FILE__, __LINE__, sample); COMMENT(sample.getInfo(guard).c_str()); sample.start(guard); TEST(sample.isRunning(guard)); } // Wait for CA connection size_t wait = 0; while (wait < 10 && sample.getPVState() != ProcessVariable::CONNECTED) { epicsThreadSleep(0.1); { Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.flush(ctx_guard); } ++wait; } TEST(sample.getPVState() == ProcessVariable::CONNECTED); { Guard guard(__FILE__, __LINE__, sample); COMMENT(sample.getInfo(guard).c_str()); COMMENT("Disconnecting, expecting 'disconnected' and 'off' events."); sample.stop(guard); COMMENT(sample.getInfo(guard).c_str()); TEST(sample.getSampleCount(guard) == 2); } // IDEA: Test the tmp buffer stuff in SampleMechanism::pvConnected? // Not feasable with the PV which uses ChannelAccess. // That would require a fake PV, one where we can // manually trigger the connect/disconnect/value change. TEST(sample.getPVState() == ProcessVariable::INIT); TEST_OK; }
void start() { Guard guard(__FILE__, __LINE__, mutex); LOG_ASSERT(!pv); pv = new ProcessVariable(ctx, "janet"); pv->addListener(this); { Guard pv_guard(__FILE__, __LINE__, *pv); pv->start(pv_guard); } Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.flush(ctx_guard); }
ProcessVariable::~ProcessVariable() { if (state != INIT) { LOG_MSG("ProcessVariable(%s) destroyed without stopping!\n", getName().c_str()); // Bad situation. // If you don't clear the channel, CA might still invoke callbacks // for a ProcessVariable instance that is no more. // On the other hand, who knows if the 'id' is valid // and we won't crash in ca_clear_channel? // We assume that the id _is_ valid, so protect against further // callbacks. ca_clear_channel should remove all subscriptions. if (id != 0) { ca_clear_channel(id); id = 0; } } if (!state_listeners.isEmpty()) { LOG_MSG("ProcessVariable(%s) still has %zu state listeners\n", getName().c_str(), (size_t)state_listeners.size()); return; } if (!value_listeners.isEmpty()) { LOG_MSG("ProcessVariable(%s) still has %zu value listeners\n", getName().c_str(), (size_t)value_listeners.size()); return; } try { Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.decRef(ctx_guard); } catch (GenericException &e) { LOG_MSG("ProcessVariable(%s) destructor: %s\n", getName().c_str(), e.what()); } # ifdef DEBUG_PV printf("deleted ProcessVariable(%s)\n", getName().c_str()); # endif }
void ProcessVariable::stop(Guard &guard) { # ifdef DEBUG_PV printf("stop ProcessVariable(%s)\n", getName().c_str()); # endif guard.check(__FILE__, __LINE__, mutex); LOG_ASSERT(isRunning(guard)); // We'll unlock in unsubscribe(), and then for the ca_clear_channel. // At those times, an ongoing connection could invoke the connection_handler, // control_callback or subscribe. // Setting all indicators back to INIT state will cause those to bail. chid _id = id; id = 0; bool was_connected = (state == CONNECTED); state = INIT; outstanding_gets = 0; unsubscribe(guard); // Unlock around CA lib. calls to prevent deadlocks. { GuardRelease release(__FILE__, __LINE__, guard); { // If we were subscribed, this was already checked in unsubscribe()... Guard ctx_guard(__FILE__, __LINE__, ctx); LOG_ASSERT(ctx.isAttached(ctx_guard)); } try { ca_clear_channel(_id); } catch (std::exception &e) { LOG_MSG("ProcessVariable::stop(%s): %s\n", getName().c_str(), e.what()); } catch (...) { LOG_MSG("ProcessVariable::stop(%s): Unknown Exception\n", getName().c_str()); } // If there are listeners, tell them that we are disconnected. if (was_connected) firePvDisconnected(); } }
void stop() { puts("stopping"); AutoPtr<ProcessVariable> tmp; { Guard guard(__FILE__, __LINE__, mutex); LOG_ASSERT(pv); tmp = pv; LOG_ASSERT(!pv); } { Guard pv_guard(__FILE__, __LINE__, *tmp); tmp->unsubscribe(pv_guard); tmp->stop(pv_guard); } tmp->removeListener(this); tmp = 0; Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.flush(ctx_guard); }
ProcessVariable::ProcessVariable(ProcessVariableContext &ctx, const char *name) : NamedBase(name), mutex(name, EngineLocks::ProcessVariable), ctx(ctx), state(INIT), id(0), ev_id(0), dbr_type(DBR_TIME_DOUBLE), dbr_count(1), outstanding_gets(0) { # ifdef DEBUG_PV printf("new ProcessVariable(%s)\n", getName().c_str()); # endif { Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.incRef(ctx_guard); } // Set some default info to match the default getDbrType/Count ctrl_info.setNumeric(0, "?", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); }
void ProcessVariable::start(Guard &guard) { guard.check(__FILE__, __LINE__, mutex); # ifdef DEBUG_PV printf("start ProcessVariable(%s)\n", getName().c_str()); # endif LOG_ASSERT(! isRunning(guard)); LOG_ASSERT(state == INIT); state = DISCONNECTED; // Unlock around CA lib. calls to prevent deadlocks in callbacks. int status; chid _id; { GuardRelease release(__FILE__, __LINE__, guard); { try { status = ca_create_channel(getName().c_str(), connection_handler, this, CA_PRIORITY_ARCHIVE, &_id); } catch (std::exception &e) { LOG_MSG("ProcessVariable::start(%s): %s\n", getName().c_str(), e.what()); } catch (...) { LOG_MSG("ProcessVariable::start(%s): Unknown Exception\n", getName().c_str()); } Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.requestFlush(ctx_guard); } } id = _id; if (status != ECA_NORMAL) LOG_MSG("'%s': ca_create_channel failed, status %s\n", getName().c_str(), ca_message(status)); }
void pvConnected(ProcessVariable &pv, const epicsTime &when) { { Guard guard(__FILE__, __LINE__, mutex); connected = true; puts("\nConnected"); } if (stop_in_connect) { puts("Stop in connect"); hack.signal(); } { Guard pv_guard(__FILE__, __LINE__, pv); pv.subscribe(pv_guard); } { Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.flush(ctx_guard); } epicsThreadSleep(linger); }
const char *ProcessVariable::getCAStateStr(Guard &guard) const { guard.check(__FILE__, __LINE__, mutex); chid _id = id; if (_id == 0) return "Not Initialized"; enum channel_state cs; { // Unlock while dealing with CAC. GuardRelease release(__FILE__, __LINE__, guard); { Guard ctx_guard(__FILE__, __LINE__, ctx); LOG_ASSERT(ctx.isAttached(ctx_guard)); } cs = ca_state(_id); } switch (cs) { case cs_never_conn: return "Never Conn."; case cs_prev_conn: return "Prev. Conn."; case cs_conn: return "Connected"; case cs_closed: return "Closed"; default: return "unknown"; } }
// Channel Access callback void ProcessVariable::connection_handler(struct connection_handler_args arg) { ProcessVariable *me = (ProcessVariable *) ca_puser(arg.chid); LOG_ASSERT(me != 0); try { if (arg.op == CA_OP_CONN_DOWN) { // Connection is down { Guard guard(__FILE__, __LINE__, *me); me->state = DISCONNECTED; } me->firePvDisconnected(); return; } { // else: Connection is 'up' Guard guard(__FILE__, __LINE__, *me); if (me->id == 0) { LOG_MSG("ProcessVariable(%s) received " "unexpected connection_handler\n", me->getName().c_str()); return; } # ifdef DEBUG_PV printf("ProcessVariable(%s) getting control info\n", me->getName().c_str()); # endif me->state = GETTING_INFO; // Get control information for this channel. // Bug in (at least older) CA: Works only for 1 element, // even for array channels. // We are in CAC callback, and managed to lock ourself, // so it should be OK to issue another CAC call. try { int status = ca_array_get_callback( ca_field_type(me->id)+DBR_CTRL_STRING, 1 /* ca_element_count(me->ch_id) */, me->id, control_callback, me); if (status != ECA_NORMAL) { LOG_MSG("ProcessVariable('%s') connection_handler error %s\n", me->getName().c_str(), ca_message (status)); return; } } catch (std::exception &e) { LOG_MSG("ProcessVariable::connection_handler(%s): %s\n", me->getName().c_str(), e.what()); } catch (...) { LOG_MSG("ProcessVariable::connection_handler(%s): " "Unknown Exception\n", me->getName().c_str()); } } Guard ctx_guard(__FILE__, __LINE__, me->ctx); me->ctx.requestFlush(ctx_guard); } catch (GenericException &e) { LOG_MSG("ProcessVariable(%s) connection_handler exception:\n%s\n", me->getName().c_str(), e.what()); } }
void ProcessVariable::subscribe(Guard &guard) { guard.check(__FILE__, __LINE__, mutex); if (dbr_type == 0) throw GenericException(__FILE__, __LINE__, "Cannot subscribe to %s, never connected", getName().c_str()); // Prevent multiple subscriptions if (isSubscribed(guard)) return; // While we were unlocked, a disconnect or stop() could have happend, // in which case we need to bail out. if (id == 0 || state != CONNECTED) { LOG_MSG("'%s': Skipped subscription, state %s, id 0x%lu.\n", getName().c_str(), getStateStr(guard), (unsigned long) id); return; } chid _id = id; evid _ev_id = 0; DbrType _type = dbr_type; DbrCount _count = dbr_count; { // Release around CA call?? // -- GuardRelease release(__FILE__, __LINE__, guard); // Right now, could a stop() and ca_clear_channel(id) happen, // so that ca_create_subscription() uses an invalid id? // // Task A, CAC client: // control_callback, pvConnected, subscribe // // Task B, Engine or HTTPD: // stop, clear_channel // // LockTest.cpp indicates that the clear_channel() will wait // for the CAC library to leabe the control_callback. // So even though we unlock the ProcessVariable and somebody // could invoke stop() and set id=0, we have the copied _id, // and the ca_clear_channel(id) won't happen until we leave // the control_callback. // This of course only handles the use case of the engine // where subscribe is invoked from control_callback & pvConnected. // to be on the safe side, we keep the guard and prevent a stop(), // until we find a deadlock that forces us to reconsider.... { int status; try { status = ca_create_subscription(_type, _count, _id, DBE_LOG | DBE_ALARM, value_callback, this, &_ev_id); } catch (std::exception &e) { LOG_MSG("ProcessVariable::subscribe(%s): %s\n", getName().c_str(), e.what()); } catch (...) { LOG_MSG("ProcessVariable::subscribe(%s): Unknown Exception\n", getName().c_str()); } if (status != ECA_NORMAL) { LOG_MSG("%s: ca_create_subscription failed: %s\n", getName().c_str(), ca_message(status)); return; } Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.requestFlush(ctx_guard); } } ev_id = _ev_id; LOG_ASSERT(ev_id != 0); #ifdef CHECK_EVID void *user = peek_evid_userptr(ev_id); LOG_ASSERT(user == this); #endif }
TEST_CASE process_variable() { size_t wait = 0; AutoPtr<ProcessVariableContext> ctx(new ProcessVariableContext()); AutoPtr<PVTestPVListener> pvl(new PVTestPVListener(1)); AutoPtr<PVTestPVListener> pvl2(new PVTestPVListener(2)); { AutoPtr<ProcessVariable> pv(new ProcessVariable(*ctx, "janet")); AutoPtr<ProcessVariable> pv2(new ProcessVariable(*ctx, "janet")); pv->addListener(pvl); pv2->addListener(pvl2); { Guard ctx_guard(__FILE__, __LINE__, *ctx); TEST(ctx->getRefs(ctx_guard) == 2); } TEST(pv->getName() == "janet"); TEST(pv2->getName() == "janet"); { Guard pv_guard(__FILE__, __LINE__, *pv); Guard pv_guard2(__FILE__, __LINE__, *pv2); TEST(pv->getState(pv_guard) == ProcessVariable::INIT); TEST(pv2->getState(pv_guard2) == ProcessVariable::INIT); pv->start(pv_guard); pv2->start(pv_guard2); // PV ought to stay disconnected until the connection // gets sent out by the context in the following // flush handling loop. TEST(pv->getState(pv_guard) == ProcessVariable::DISCONNECTED); TEST(pv2->getState(pv_guard2) == ProcessVariable::DISCONNECTED); } while (pvl->connected == false || pvl2->connected == false) { epicsThreadSleep(0.1); { Guard ctx_guard(__FILE__, __LINE__, *ctx); if (ctx->isFlushRequested(ctx_guard)) ctx->flush(ctx_guard); } ++wait; if (wait > 100) break; } { Guard pv_guard(__FILE__, __LINE__, *pv); Guard pv_guard2(__FILE__, __LINE__, *pv2); // Unclear if future releases might automatically connect // without flush. TEST(pv->getState(pv_guard) == ProcessVariable::CONNECTED); TEST(pv2->getState(pv_guard2) == ProcessVariable::CONNECTED); } // 'get' puts(" Getting 1...."); for (int i=0; i<3; ++i) { pvl->values = 0; { Guard pv_guard(__FILE__, __LINE__, *pv); pv->getValue(pv_guard); } wait = 0; while (pvl->values < 1) { epicsThreadSleep(0.1); { Guard ctx_guard(__FILE__, __LINE__, *ctx); if (ctx->isFlushRequested(ctx_guard)) ctx->flush(ctx_guard); } ++wait; if (wait > 10) break; } } epicsThreadSleep(1.0); // 'monitor' puts(" Monitoring 1, getting 2...."); { Guard pv_guard(__FILE__, __LINE__, *pv); Guard pv_guard2(__FILE__, __LINE__, *pv2); pvl->values = 0; pv->subscribe(pv_guard); pvl2->values = 0; pv2->getValue(pv_guard2); } wait = 0; size_t num = 4; if (getenv("REPEAT")) num = atoi(getenv("REPEAT")); while (pvl->values < num) { epicsThreadSleep(0.1); { Guard ctx_guard(__FILE__, __LINE__, *ctx); if (ctx->isFlushRequested(ctx_guard)) ctx->flush(ctx_guard); } ++wait; if (wait > 10*num) break; } TEST(pvl->values > 0); TEST(pvl2->values == 1); { Guard pv_guard(__FILE__, __LINE__, *pv); Guard pv_guard2(__FILE__, __LINE__, *pv2); pv->unsubscribe(pv_guard); pv->stop(pv_guard); pv2->stop(pv_guard2); } pv->removeListener(pvl); pv2->removeListener(pvl2); } { Guard ctx_guard(__FILE__, __LINE__, *ctx); TEST(ctx->getRefs(ctx_guard) == 0); } TEST_OK; }
TEST_CASE test_sample_mechanism_monitored_get() { ScanList scan_list; EngineConfig config; ProcessVariableContext ctx; SampleMechanismMonitoredGet sample(config, ctx, scan_list, "janet", 1.0); TEST(sample.getName() == "janet"); COMMENT("Trying to connect..."); { Guard guard(__FILE__, __LINE__, sample); COMMENT(sample.getInfo(guard).c_str()); sample.start(guard); TEST(sample.isRunning(guard)); } // Wait for CA connection size_t wait = 0; while (wait < 10 && sample.getPVState() != ProcessVariable::CONNECTED) { epicsThreadSleep(0.1); { Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.flush(ctx_guard); } ++wait; } TEST(sample.getPVState() == ProcessVariable::CONNECTED); COMMENT("Waiting for 3 samples..."); while (wait < 50) { epicsThreadSleep(0.1); { Guard ctx_guard(__FILE__, __LINE__, ctx); ctx.flush(ctx_guard); } { Guard guard(__FILE__, __LINE__, sample); if (sample.getSampleCount(guard) >= 3) break; } ++wait; } TEST(wait < 50); COMMENT("Disconnecting."); size_t n1, n2; { Guard guard(__FILE__, __LINE__, sample); COMMENT(sample.getInfo(guard).c_str()); n1 = sample.getSampleCount(guard); sample.stop(guard); COMMENT(sample.getInfo(guard).c_str()); n2 = sample.getSampleCount(guard); } int added_samples = n2 - n1; COMMENT("Check if 'disconnected' and 'off' were added"); TEST(added_samples == 2); TEST(sample.getPVState() == ProcessVariable::INIT); TEST_OK; }