AMPS C/C++ Client Class Reference
AMPS C/C++ Client Version 5.3.4.0
HAClientImpl.hpp
1 //
3 // Copyright (c) 2010-2023 60East Technologies Inc., All Rights Reserved.
4 //
5 // This computer software is owned by 60East Technologies Inc. and is
6 // protected by U.S. copyright laws and other laws and by international
7 // treaties. This computer software is furnished by 60East Technologies
8 // Inc. pursuant to a written license agreement and may be used, copied,
9 // transmitted, and stored only in accordance with the terms of such
10 // license agreement and with the inclusion of the above copyright notice.
11 // This computer software or any other copies thereof may not be provided
12 // or otherwise made available to any other person.
13 //
14 // U.S. Government Restricted Rights. This computer software: (a) was
15 // developed at private expense and is in all respects the proprietary
16 // information of 60East Technologies Inc.; (b) was not developed with
17 // government funds; (c) is a trade secret of 60East Technologies Inc.
18 // for all purposes of the Freedom of Information Act; and (d) is a
19 // commercial item and thus, pursuant to Section 12.212 of the Federal
20 // Acquisition Regulations (FAR) and DFAR Supplement Section 227.7202,
21 // Government's use, duplication or disclosure of the computer software
22 // is subject to the restrictions set forth by 60East Technologies Inc..
23 //
25 
26 #ifndef _HACLIENTIMPL_H_
27 #define _HACLIENTIMPL_H_
28 
29 #include <typeinfo>
30 #include <amps/ampsplusplus.hpp>
31 #include <amps/ServerChooser.hpp>
34 #if __cplusplus >= 201103L || _MSC_VER >= 1900
35 #include <atomic>
36 #endif
37 
38 namespace AMPS
39 {
40 
41  class HAClientImpl : public ClientImpl
42  {
43  public:
44  HAClientImpl(const std::string& name_)
45  : ClientImpl(name_), _timeout(AMPS_HACLIENT_TIMEOUT_DEFAULT)
46  , _reconnectDelay(AMPS_HACLIENT_RECONNECT_DEFAULT)
47  , _reconnectDelayStrategy(new ExponentialDelayStrategy(_reconnectDelay))
48  , _disconnected(false)
49  {
50 #ifdef AMPS_USE_FUNCTIONAL
51  setDisconnectHandler(HADisconnectHandler());
52 #else
53  setDisconnectHandler(AMPS::DisconnectHandler(&HADisconnectHandler::invoke, NULL));
54 #endif
55  setSubscriptionManager(new MemorySubscriptionManager());
56  }
57 
58  ~HAClientImpl()
59  {
60  _disconnected = true;
61  _cleanup();
62  }
63 
64  void setTimeout(int timeout_)
65  {
66  _timeout = timeout_;
67  }
68 
69  int getTimeout() const
70  {
71  return _timeout;
72  }
73 
74  unsigned int getReconnectDelay(void) const
75  {
76  return _reconnectDelay;
77  }
78 
79  void setReconnectDelay(unsigned int reconnectDelay_)
80  {
81  _reconnectDelay = reconnectDelay_;
82  setReconnectDelayStrategy(new FixedDelayStrategy(
83  (unsigned int)reconnectDelay_));
84  }
85 
86  void setReconnectDelayStrategy(const ReconnectDelayStrategy& strategy_)
87  {
88  _reconnectDelayStrategy = strategy_;
89  }
90 
91  ReconnectDelayStrategy getReconnectDelayStrategy(void) const
92  {
93  return _reconnectDelayStrategy;
94  }
95 
96  std::string getLogonOptions(void) const
97  {
98  return _logonOptions;
99  }
100 
101  void setLogonOptions(const std::string& logonOptions_)
102  {
103  _logonOptions = logonOptions_;
104  }
105 
106  void setLogonOptions(const char* logonOptions_)
107  {
108  _logonOptions = logonOptions_;
109  }
110 
111  ServerChooser getServerChooser() const
112  {
113  return _serverChooser;
114  }
115 
116  void setServerChooser(const ServerChooser& serverChooser_)
117  {
118  _serverChooser = serverChooser_;
119  }
120 
121  class HADisconnectHandler
122  {
123  public:
124  HADisconnectHandler() {}
125  static void invoke(Client& client, void* );
126 #ifdef AMPS_USE_FUNCTIONAL
127  void operator()(Client& client)
128  {
129  invoke(client, NULL);
130  }
131 #endif
132  };
133  void connectAndLogon()
134  {
135  Lock<Mutex> l(_connectAndLogonLock);
136  // AC-1030 In case this is called on a client after delay strategy caused a failure.
137  _reconnectDelayStrategy.reset();
138  try
139  {
140  while (true)
141  {
142  _disconnected = false;
143  connectAndLogonInternal();
144  try
145  {
146  // Resubscribe
147  if (_subscriptionManager)
148  {
149  Client c(this);
150  _subscriptionManager->resubscribe(c);
151  broadcastConnectionStateChanged(
152  ConnectionStateListener::Resubscribed);
153  }
154  return;
155  }
156  catch (const AMPSException& subEx)
157  {
158  // Keep receive thread from reconnecting
159  _disconnected = true;
160  _serverChooser.reportFailure(subEx, getConnectionInfo());
161  ClientImpl::setDisconnected();
162  }
163  }
164  }
165  catch (...)
166  {
167  // Failure, make sure we're disconnected
168  disconnect();
169  throw;
170  }
171  }
172 
173  virtual void connect(const std::string& /*uri*/)
174  {
175  connectAndLogon();
176  }
177 
178  virtual std::string logon(long /*timeout_*/, Authenticator& /*authenticator_*/,
179  const char* /*options_*/)
180  {
181  if (_disconnected)
182  {
183  throw DisconnectedException("Attempt to call logon on a disconnected HAClient. Use connectAndLogon() instead.");
184  }
185  throw AlreadyConnectedException("Attempt to call logon on an HAClient. Use connectAndLogon() instead.");
186  }
187 
188  static void HADoNothingDisconnectHandler(amps_handle /*client*/,
189  void* /*data*/)
190  {
191  ;
192  }
193 
194  class DisconnectHandlerDisabler
195  {
196  public:
197  DisconnectHandlerDisabler()
198  : _pClient(NULL), _queueAckTimeout(0) { }
199  DisconnectHandlerDisabler(HAClientImpl* pClient_)
200  : _pClient(pClient_)
201  {
202  setHandler();
203  _queueAckTimeout = _pClient->getAckTimeout();
204  _pClient->setAckTimeout(0);
205  }
206  ~DisconnectHandlerDisabler()
207  {
208  clear();
209  }
210  void clear()
211  {
212  if (_pClient)
213  {
215  _pClient->getHandle(),
216  (amps_handler)ClientImpl::ClientImplDisconnectHandler,
217  _pClient);
218  if (_queueAckTimeout)
219  {
220  _pClient->setAckTimeout(_queueAckTimeout);
221  _queueAckTimeout = 0;
222  }
223  _pClient = NULL;
224  }
225  }
226  void setClient(HAClientImpl* pClient_)
227  {
228  if (!_pClient)
229  {
230  _pClient = pClient_;
231  setHandler();
232  _queueAckTimeout = _pClient->getAckTimeout();
233  _pClient->setAckTimeout(0);
234  amps_client_disconnect(_pClient->getHandle());
235  }
236  }
237  void setHandler()
238  {
240  _pClient->getHandle(),
241  (amps_handler)HAClientImpl::HADoNothingDisconnectHandler,
242  _pClient);
243  }
244  private:
245  HAClientImpl* _pClient;
246  int _queueAckTimeout;
247  };
248 
249  void connectAndLogonInternal()
250  {
251  if (!_serverChooser.isValid())
252  {
253  throw ConnectionException("No server chooser registered with HAClient");
254  }
255  {
256  DisconnectHandlerDisabler disconnectDisabler;
257  while (!_disconnected)
258  {
259  std::string uri = _serverChooser.getCurrentURI();
260  if (uri.empty())
261  {
262  throw ConnectionException("No AMPS instances available for connection. " + _serverChooser.getError());
263  }
264  Authenticator& auth = _serverChooser.getCurrentAuthenticator();
265  // Begin locked section - see AC-1017
266  Lock<Mutex> l(_connectLock);
267  _sleepBeforeConnecting(uri);
268  try
269  {
270  // Check if another thread disconnected or already connected
271  if (_disconnected || _connected)
272  {
273  return;
274  }
275  // Temporarily unset the disconnect handler since we will loop
276  disconnectDisabler.setClient((HAClientImpl*)this);
277  // Connect and logon while holding the _lock
278  {
279  Lock<Mutex> clientLock(_lock);
280  ClientImpl::_connect(uri);
281  if (_logonOptions.empty())
282  {
283  ClientImpl::_logon(_timeout, auth);
284  }
285  else
286  {
287  ClientImpl::_logon(_timeout, auth, _logonOptions.c_str());
288  }
289  }
290  disconnectDisabler.clear();
291  try
292  {
293  _serverChooser.reportSuccess(getConnectionInfo());
294  _reconnectDelayStrategy.reset();
295  }
296  catch (const AMPSException& e)
297  {
298  ClientImpl::disconnect();
299  throw AMPSException(e);
300  }
301  break;
302  }
303  catch (const AMPSException& ex)
304  {
305  Unlock<Mutex> u(_connectLock);
306  ConnectionInfo ci = getConnectionInfo();
307  // Substitute the URI on the connection info with the one we attempted
308  ci["client.uri"] = uri;
309  _serverChooser.reportFailure(ex, ci);
310  try
311  {
312  ClientImpl::setDisconnected();
313  }
314  catch (const std::exception& e)
315  {
316  try
317  {
318  _exceptionListener->exceptionThrown(e);
319  }
320  catch (...) { } // -V565
321  }
322  catch (...)
323  {
324  try
325  {
326  _exceptionListener->exceptionThrown(UnknownException("Unknown exception calling setDisconnected"));
327  }
328  catch (...) { } // -V565
329  }
330  }
331  }
332  }
333  return;
334  }
335 
336  ConnectionInfo gatherConnectionInfo() const
337  {
338  return getConnectionInfo();
339  }
340 
341  ConnectionInfo getConnectionInfo() const
342  {
343  ConnectionInfo info = ClientImpl::getConnectionInfo();
344  std::ostringstream writer;
345 
346  writer << getReconnectDelay();
347  info["haClient.reconnectDelay"] = writer.str();
348  writer.clear(); writer.str("");
349  writer << _timeout;
350  info["haClient.timeout"] = writer.str();
351 
352  return info;
353  }
354 
355  bool disconnected() const
356  {
357  return _disconnected;
358  }
359  private:
360 
361  void disconnect()
362  {
363  {
364  Lock<Mutex> l(_connectLock);
365  _disconnected = true;
366  }
367  ClientImpl::disconnect();
368  }
369  void _millisleep(unsigned int millis_)
370  {
371  if (millis_ == 0)
372  {
373  return;
374  }
375  double waitTime = (double)millis_;
376  Timer timer(waitTime);
377  timer.start();
378  while (!timer.checkAndGetRemaining(&waitTime))
379  {
380  if (waitTime - 1000.0 > 0.0)
381  {
382  AMPS_USLEEP(1000000);
383  }
384  else
385  {
386  AMPS_USLEEP(1000UL * (unsigned int)waitTime);
387  }
388  amps_invoke_waiting_function();
389  }
390  }
391  void _sleepBeforeConnecting(const std::string& uri_)
392  {
393  try
394  {
395  _millisleep(
396  _reconnectDelayStrategy.getConnectWaitDuration(uri_));
397  }
398  catch (const ConnectionException&)
399  {
400  throw;
401  }
402  catch (const std::exception& ex_)
403  {
404  _exceptionListener->exceptionThrown(ex_);
405  throw ConnectionException(ex_.what());
406  }
407  catch (...)
408  {
409  throw ConnectionException("Unknown exception thrown by "
410  "the HAClient's delay strategy.");
411  }
412  }
413 
414  Mutex _connectLock;
415  Mutex _connectAndLogonLock;
416  int _timeout;
417  unsigned int _reconnectDelay;
418  ReconnectDelayStrategy _reconnectDelayStrategy;
419  ServerChooser _serverChooser;
420 #if __cplusplus >= 201103L || _MSC_VER >= 1900
421  std::atomic<bool> _disconnected;
422 #else
423  volatile bool _disconnected;
424 #endif
425  std::string _logonOptions;
426 
427  }; // class HAClientImpl
428 
429 }// namespace AMPS
430 
431 #endif //_HACLIENTIMPL_H_
432 
AMPSDLL void amps_client_set_disconnect_handler(amps_handle client, amps_handler disconnectHandler, void *userData)
Sets the disconnect handler function to be called when a disconnect occurs.
AMPSDLL void amps_client_disconnect(amps_handle handle)
Disconnects from the AMPS server, if connected.
Provides AMPS::MemorySubscriptionManager, used by an AMPS::HAClient to resubmit subscriptions if conn...
void * amps_handle
Opaque handle type used to refer to objects in the AMPS api.
Definition: amps.h:195
Core type, function, and class declarations for the AMPS C++ client.
Provides AMPS::ReconnectDelayStrategy, called by an AMPS::HAClient to determine how long to wait betw...
Provides AMPS::ServerChooser, the abstract base class that defines the interface that an AMPS::HAClie...
Definition: ampsplusplus.hpp:106