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);
 }
예제 #11
0
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";
    }
}
예제 #12
0
// 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());
    }    
}
예제 #13
0
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;
}