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); } }
void GroupInfo::addChannel(Guard &group_guard, ArchiveChannel *channel) { group_guard.check(__FILE__, __LINE__, mutex); // Is Channel already in group? stdList<ArchiveChannel *>::iterator i; for (i=channels.begin(); i!=channels.end(); ++i) if (*i == channel) return; channels.push_back(channel); }
// called by ArchiveChannel void GroupInfo::decConnected(Guard &group_guard, ArchiveChannel &pv) { group_guard.check(__FILE__, __LINE__, mutex); if (num_connected <= 0) throw GenericException(__FILE__, __LINE__, "Group %s connect count runs below 0 " "on decrement from '%s'", getName().c_str(), pv.getName().c_str()); --num_connected; }
// called by ArchiveChannel void GroupInfo::incConnected(Guard &group_guard, ArchiveChannel &pv) { group_guard.check(__FILE__, __LINE__, mutex); ++num_connected; if (num_connected > channels.size()) throw GenericException(__FILE__, __LINE__, "Group %s connect count is %zu out of %zu " "on increment from '%s'", getName().c_str(), (size_t)num_connected, (size_t)channels.size(), pv.getName().c_str()); }
// called by ArchiveChannel void GroupInfo::disable(Guard &group_guard, ArchiveChannel *cause, const epicsTime &when) { group_guard.check(__FILE__, __LINE__, mutex); LOG_MSG("'%s' disables group '%s'\n", cause->getName().c_str(), getName().c_str()); ++disable_count; if (disable_count != 1) // Was already disabled? return; // Disable all channels in this group stdList<ArchiveChannel *>::iterator ci; for (ci = channels.begin(); ci != channels.end(); ++ci) { ArchiveChannel *c = *ci; Guard guard(__FILE__, __LINE__, *c); c->disable(guard, when); } }
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 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)); }
// called by ArchiveChannel void GroupInfo::enable(Guard &group_guard, ArchiveChannel *cause, const epicsTime &when) { group_guard.check(__FILE__, __LINE__, mutex); LOG_MSG("'%s' enables group '%s'\n", cause->getName().c_str(), getName().c_str()); if (disable_count <= 0) { LOG_MSG("Group %s is not disabled, ERROR!\n", getName().c_str()); return; } --disable_count; if (disable_count > 0) // Still disabled? return; // Enable all channels in this group stdList<ArchiveChannel *>::iterator ci; for (ci = channels.begin(); ci != channels.end(); ++ci) { ArchiveChannel *c = *ci; Guard guard(__FILE__, __LINE__, *c); c->enable(guard, when); } }
ProcessVariable::State ProcessVariable::getState(Guard &guard) const { guard.check(__FILE__, __LINE__, mutex); return state; }
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 }
bool ProcessVariable::isRunning(Guard &guard) { guard.check(__FILE__, __LINE__, mutex); return id != 0; }