pva2pva  1.4.1
 All Classes Functions Variables Pages
pvalink.cpp
1 
2 #include <set>
3 #include <map>
4 
5 #define EPICS_DBCA_PRIVATE_API
6 #include <epicsGuard.h>
7 #include <dbAccess.h>
8 #include <dbCommon.h>
9 #include <dbLink.h>
10 #include <dbScan.h>
11 #include <errlog.h>
12 #include <initHooks.h>
13 #include <alarm.h>
14 #include <epicsExit.h>
15 #include <epicsAtomic.h>
16 #include <link.h>
17 #include <dbJLink.h>
18 #include <epicsUnitTest.h>
19 #include <epicsString.h>
20 
21 #include <epicsStdio.h> /* redirects stdout/stderr */
22 
23 #include <pv/pvAccess.h>
24 #include <pv/clientFactory.h>
25 #include <pv/iocshelper.h>
26 #include <pv/reftrack.h>
27 #include <pva/client.h>
28 
29 #include "pv/qsrv.h"
30 #include "helper.h"
31 #include "pvif.h"
32 #include "pvalink.h"
33 
34 #include <epicsExport.h> /* defines epicsExportSharedSymbols */
35 
36 #if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0)
37 # define HAVE_SHUTDOWN_HOOKS
38 #endif
39 
40 int pvaLinkDebug;
41 int pvaLinkIsolate;
42 
43 using namespace pvalink;
44 
45 namespace {
46 
47 // halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown())
48 static void shutdownStep1()
49 {
50  // no locking here as we assume that shutdown doesn't race startup
51  if(!pvaGlobal) return;
52 
53  pvaGlobal->queue.close();
54 }
55 
56 // Cleanup pvaGlobal, including PVA client and QSRV providers ahead of PDB cleanup
57 // specifically QSRV provider must be free'd prior to db_cleanup_events()
58 static void shutdownStep2()
59 {
60  if(!pvaGlobal) return;
61 
62  {
63  Guard G(pvaGlobal->lock);
64  if(pvaGlobal->channels.size()) {
65  fprintf(stderr, "pvaLink leaves %zu channels open\n",
66  pvaGlobal->channels.size());
67  }
68  }
69 
70  delete pvaGlobal;
71  pvaGlobal = NULL;
72 }
73 
74 #ifndef HAVE_SHUTDOWN_HOOKS
75 static void stopPVAPool(void*)
76 {
77  try {
78  shutdownStep1();
79  }catch(std::exception& e){
80  fprintf(stderr, "Error while stopping PVA link pool : %s\n", e.what());
81  }
82 }
83 
84 static void finalizePVA(void*)
85 {
86  try {
87  shutdownStep2();
88  }catch(std::exception& e){
89  fprintf(stderr, "Error initializing pva link handling : %s\n", e.what());
90  }
91 }
92 #endif
93 
94 /* The Initialization game...
95  *
96  * # Parse links during dbPutString() (calls our jlif*)
97  * # announce initHookAfterCaLinkInit
98  * # dbChannelInit() (needed for QSRV to work)
99  * # Re-parse links (calls to our jlif*)
100  * # Open links. Calls jlif::get_lset() and then lset::openLink()
101  * # announce initHookAfterInitDatabase
102  * # ... scan threads start ...
103  * # announce initHookAfterIocBuilt
104  */
105 void initPVALink(initHookState state)
106 {
107  try {
108  if(state==initHookAfterCaLinkInit) {
109  // before epicsExit(exitDatabase),
110  // so hook registered here will be run after iocShutdown()
111  // which closes links
112  if(pvaGlobal) {
113  cantProceed("# Missing call to testqsrvShutdownOk() and/or testqsrvCleanup()");
114  }
115  pvaGlobal = new pvaGlobal_t;
116 
117 #ifndef HAVE_SHUTDOWN_HOOKS
118  static bool atexitInstalled;
119  if(!atexitInstalled) {
120  epicsAtExit(finalizePVA, NULL);
121  atexitInstalled = true;
122  }
123 #endif
124 
125  } else if(state==initHookAfterInitDatabase) {
126  pvac::ClientProvider local("server:QSRV"),
127  remote("pva");
128  pvaGlobal->provider_local = local;
129  pvaGlobal->provider_remote = remote;
130 
131  } else if(state==initHookAfterIocBuilt) {
132  // after epicsExit(exitDatabase)
133  // so hook registered here will be run before iocShutdown()
134 
135 #ifndef HAVE_SHUTDOWN_HOOKS
136  epicsAtExit(stopPVAPool, NULL);
137 #endif
138 
139  Guard G(pvaGlobal->lock);
140  pvaGlobal->running = true;
141 
142  for(pvaGlobal_t::channels_t::iterator it(pvaGlobal->channels.begin()), end(pvaGlobal->channels.end());
143  it != end; ++it)
144  {
145  std::tr1::shared_ptr<pvaLinkChannel> chan(it->second.lock());
146  if(!chan) continue;
147 
148  chan->open();
149  }
150 #ifdef HAVE_SHUTDOWN_HOOKS
151  } else if(state==initHookAtShutdown) {
152  shutdownStep1();
153 
154  } else if(state==initHookAfterShutdown) {
155  shutdownStep2();
156 #endif
157  }
158  }catch(std::exception& e){
159  cantProceed("Error initializing pva link handling : %s\n", e.what());
160  }
161 }
162 
163 } // namespace
164 
165 // halt, and clear, scan workers before dbCloseLinks() (cf. iocShutdown())
166 void testqsrvShutdownOk(void)
167 {
168  try {
169  shutdownStep1();
170  }catch(std::exception& e){
171  testAbort("Error while stopping PVA link pool : %s\n", e.what());
172  }
173 }
174 
175 void testqsrvCleanup(void)
176 {
177  try {
178  shutdownStep2();
179  }catch(std::exception& e){
180  testAbort("Error initializing pva link handling : %s\n", e.what());
181  }
182 }
183 
184 void testqsrvWaitForLinkEvent(struct link *plink)
185 {
186  std::tr1::shared_ptr<pvaLinkChannel> lchan;
187  {
188  DBScanLocker lock(plink->precord);
189 
190  if(plink->type!=JSON_LINK || !plink->value.json.jlink || plink->value.json.jlink->pif!=&lsetPVA) {
191  testAbort("Not a PVA link");
192  }
193  pvaLink *pval = static_cast<pvaLink*>(plink->value.json.jlink);
194  lchan = pval->lchan;
195  }
196  if(lchan) {
197  lchan->run_done.wait();
198  }
199 }
200 
201 extern "C"
202 void dbpvar(const char *precordname, int level)
203 {
204  try {
205  if(!pvaGlobal) {
206  printf("PVA links not initialized\n");
207  return;
208  }
209 
210  if (!precordname || precordname[0] == '\0' || !strcmp(precordname, "*")) {
211  precordname = NULL;
212  printf("PVA links in all records\n\n");
213  } else {
214  printf("PVA links in record named '%s'\n\n", precordname);
215  }
216 
217  size_t nchans = 0, nlinks = 0, nconn = 0;
218 
219  pvaGlobal_t::channels_t channels;
220  {
221  Guard G(pvaGlobal->lock);
222  channels = pvaGlobal->channels; // copy snapshot
223  }
224 
225  for(pvaGlobal_t::channels_t::const_iterator it(channels.begin()), end(channels.end());
226  it != end; ++it)
227  {
228  std::tr1::shared_ptr<pvaLinkChannel> chan(it->second.lock());
229  if(!chan) continue;
230 
231  Guard G(chan->lock);
232 
233  if(precordname) {
234  // only show links fields of these records
235  bool match = false;
236  for(pvaLinkChannel::links_t::const_iterator it2(chan->links.begin()), end2(chan->links.end());
237  it2 != end2; ++it2)
238  {
239  const pvaLink *pval = *it2;
240  // plink==NULL shouldn't happen, but we are called for debugging, so be paranoid.
241  if(pval->plink && epicsStrGlobMatch(pval->plink->precord->name, precordname)) {
242  match = true;
243  nlinks++;
244  }
245  }
246  if(!match)
247  continue;
248  }
249 
250  nchans++;
251  if(chan->connected_latched)
252  nconn++;
253 
254  if(!precordname)
255  nlinks += chan->links.size();
256 
257  if(level<=0)
258  continue;
259 
260  if(level>=2 || (!chan->connected_latched && level==1)) {
261  if(chan->key.first.size()<=28) {
262  printf("%28s ", chan->key.first.c_str());
263  } else {
264  printf("%s\t", chan->key.first.c_str());
265  }
266 
267  printf("conn=%c %zu disconnects, %zu type changes",
268  chan->connected_latched?'T':'F',
269  chan->num_disconnect,
270  chan->num_type_change);
271  if(chan->op_put.valid()) {
272  printf(" Put");
273  }
274 
275  if(level>=3) {
276  printf(", provider '%s'", chan->providerName.c_str());
277  }
278  printf("\n");
279  // level 4 reserved for channel/provider details
280 
281  if(level>=5) {
282  for(pvaLinkChannel::links_t::const_iterator it2(chan->links.begin()), end2(chan->links.end());
283  it2 != end2; ++it2)
284  {
285  const pvaLink *pval = *it2;
286 
287  if(!pval->plink)
288  continue;
289  else if(precordname && !epicsStrGlobMatch(pval->plink->precord->name, precordname))
290  continue;
291 
292  const char *fldname = "???";
293  pdbRecordIterator rec(pval->plink->precord);
294  for(bool done = !!dbFirstField(&rec.ent, 0); !done; done = !!dbNextField(&rec.ent, 0))
295  {
296  if(rec.ent.pfield == (void*)pval->plink) {
297  fldname = rec.ent.pflddes->name;
298  break;
299  }
300  }
301 
302  printf("%*s%s.%s", 30, "", pval->plink ? pval->plink->precord->name : "<NULL>", fldname);
303 
304  switch(pval->pp) {
305  case pvaLinkConfig::NPP: printf(" NPP"); break;
306  case pvaLinkConfig::Default: printf(" Def"); break;
307  case pvaLinkConfig::PP: printf(" PP"); break;
308  case pvaLinkConfig::CP: printf(" CP"); break;
309  case pvaLinkConfig::CPP: printf(" CPP"); break;
310  }
311  switch(pval->ms) {
312  case pvaLinkConfig::NMS: printf(" NMS"); break;
313  case pvaLinkConfig::MS: printf(" MS"); break;
314  case pvaLinkConfig::MSI: printf(" MSI"); break;
315  }
316 
317  printf(" Q=%u pipe=%c defer=%c time=%c retry=%c morder=%d\n",
318  unsigned(pval->queueSize),
319  pval->pipeline ? 'T' : 'F',
320  pval->defer ? 'T' : 'F',
321  pval->time ? 'T' : 'F',
322  pval->retry ? 'T' : 'F',
323  pval->monorder);
324  }
325  printf("\n");
326  }
327  }
328  }
329 
330  printf(" %zu/%zu channels connected used by %zu links\n",
331  nconn, nchans, nlinks);
332 
333  } catch(std::exception& e) {
334  fprintf(stderr, "Error: %s\n", e.what());
335  }
336 }
337 
338 static
339 void installPVAAddLinkHook()
340 {
341  initHookRegister(&initPVALink);
342  epics::iocshRegister<const char*, int, &dbpvar>("dbpvar", "record name", "level");
343  epics::registerRefCounter("pvaLinkChannel", &pvaLinkChannel::num_instances);
344  epics::registerRefCounter("pvaLink", &pvaLink::num_instances);
345 }
346 
347 extern "C" {
348  epicsExportRegistrar(installPVAAddLinkHook);
349  epicsExportAddress(jlif, lsetPVA);
350  epicsExportAddress(int, pvaLinkDebug);
351  epicsExportAddress(int, pvaLinkNWorkers);
352 }