/[svn]/linuxsampler/trunk/src/shell/LSCPClient.cpp
ViewVC logotype

Annotation of /linuxsampler/trunk/src/shell/LSCPClient.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3766 - (hide annotations) (download)
Mon Apr 6 12:41:49 2020 UTC (4 years ago) by schoenebeck
File size: 12030 byte(s)
Fixed deadlocks (e.g. when restarting engines).

* Individual thread implementations (e.g. disk thread, etc.):
  Disable thread cancellation on critical sections, e.g. when holding
  mutex locks, to prevent deadlocks if thread is stopped and/or
  restarted.

* Added TestCancel() calls to thread implementations if missing.

* No need to wrap Thread::TestCancel() calls into
  CONFIG_PTHREAD_TESTCANCEL macro conditions (since TestCancel() is
  already a stub on systems which don't have pthread_testcancel()
  available).

* If compiled for debugging: give each thread a human readable name
  to simplify debugging of multi-threading issues.

* DiskThreadBase: TestCancel() and pthread_testcancel() calls are
  per-se redundant, so only call TestCancel().

* Added missing override keywords to silent compiler warnings.

* Bumped version (2.1.1.svn54).

1 schoenebeck 2515 /*
2     * LSCP Shell
3     *
4 schoenebeck 3766 * Copyright (c) 2014 - 2020 Christian Schoenebeck
5 schoenebeck 2515 *
6     * This program is part of LinuxSampler and released under the same terms.
7     */
8    
9     #include "LSCPClient.h"
10 schoenebeck 2535 #include "lscp.h"
11 schoenebeck 2515 #include <strings.h>
12    
13 schoenebeck 2535 /**
14     * This function is called by the LSCPClient receiving thread to identify
15     * whether the received line is a) just a single line response or b) the first
16     * line of a multi line response. LSCPClient will provide this information via
17     * its multiLine() and messageComplete() methods, which are accessed by the
18     * application's main thread to find out whether it should wait until all lines
19     * of the multi line message were completely received. This strategy (to let
20     * the main app thread wait until a multi line message is really entirely
21     * received) is preferable since it would otherwise require a very complex code
22     * with a bunch of state variables in the main apps loop, which would also make
23     * it very error prone.
24     *
25     * @param line - input: the input line to check
26     * @param skipLine - output: whether @a line shall be silently ignored
27     * @returns true if @a line indicates a multi line response
28     */
29     static bool _lscpLineIndicatesMultiLineResponse(const String& line, bool& skipLine) {
30     static const String multiLineKey = LSCP_SHK_EXPECT_MULTI_LINE;
31     static const String lscpRefDocKey = "SHD:1";
32     if (line.substr(0, multiLineKey.length()) == multiLineKey) {
33     skipLine = true;
34     return true;
35     }
36     if (line.substr(0, lscpRefDocKey.length()) == lscpRefDocKey) {
37     skipLine = false;
38     return true;
39     }
40     skipLine = false;
41     return false;
42     }
43    
44     /** @brief Default constructor.
45     *
46     * Initializes a yet unconnected LSCP client. You need to call connect()
47     * afterwards to actually let it connect to some LSCP server for being able to
48     * use the client for something useful.
49     */
50 schoenebeck 2515 LSCPClient::LSCPClient() :
51     Thread(false, false, 1, -1),
52 schoenebeck 2532 hSocket(-1), m_callback(NULL), m_errorCallback(NULL),
53 schoenebeck 2535 m_multiLineExpected(false)
54 schoenebeck 2515 {
55     }
56    
57 schoenebeck 2535 /** @brief Destructor.
58     *
59     * Disconnects this client automatically if it is currently still connected to
60     * some LSCP server.
61     */
62 schoenebeck 2515 LSCPClient::~LSCPClient() {
63     disconnect();
64     }
65    
66 schoenebeck 2535 /**
67     * Let this client connect to a (already running) LSCP server listening on
68     * host name @a host and TCP port number @a port. If a connection was
69     * successfully established, this method will also start a thread which will
70     * constantly wait for receiving new data sent by the LSCP server. This thread
71     * will store all received data in an internal buffer which can then be pulled
72     * out with popLine().
73     *
74     * @param host - host name of LSCP server
75     * @param port - TCP port number of LSCP server
76     * @returns true if connection was successful, false on errors.
77     */
78 schoenebeck 2515 bool LSCPClient::connect(String host, int port) {
79     m_lineBuffer.clear();
80     m_lines.clear();
81     // resolve given host name
82     hostent* server = ::gethostbyname(host.c_str());
83     if (!server) {
84     std::cerr << "Error: Could not resolve host \"" << host << "\".\n";
85     return false;
86     }
87     // create local TCP socket
88     hSocket = ::socket(AF_INET, SOCK_STREAM, 0);
89     if (hSocket < 0) {
90     std::cerr << "Error: Could not create local socket.\n";
91     return false;
92     }
93     // TCP connect to server
94     sockaddr_in addr;
95     bzero((char*)&addr, sizeof(addr));
96     addr.sin_family = AF_INET;
97     bcopy((char*)server->h_addr, (char*)&addr.sin_addr.s_addr, server->h_length);
98     addr.sin_port = htons(port);
99     if (::connect(hSocket, (sockaddr*)&addr, sizeof(addr)) < 0) {
100     std::cerr << "Error: Could not connect to host \"" << host << "\".\n";
101 schoenebeck 2517 std::cerr << "Is linuxsampler running and listening on port " << port << " ?\n";
102 schoenebeck 2515 disconnect();
103     return false;
104     }
105     StartThread();
106     return true; // success
107     }
108    
109 schoenebeck 2535 /**
110     * Disconnect this client from the currently connected LSCP server. If there
111     * is no connection, then this method does nothing.
112     */
113 schoenebeck 2515 void LSCPClient::disconnect() {
114     if (hSocket >= 0) {
115     StopThread();
116     ::close(hSocket);
117     hSocket = -1;
118     }
119     }
120    
121 schoenebeck 2535 /**
122     * Returns true if this LSCP client is currently connected to a LSCP server.
123     */
124 schoenebeck 2515 bool LSCPClient::isConnected() const {
125     return hSocket >= 0;
126     }
127    
128 schoenebeck 2535 /**
129     * Send one byte given by @a c to the connected LSCP server asynchronously.
130     *
131     * @param c - byte (character) to be sent
132     */
133 schoenebeck 2515 bool LSCPClient::send(char c) {
134     String s;
135     s += c;
136     return send(s);
137     }
138    
139 schoenebeck 2535 /**
140     * Send a sequence of bytes given by @a s to the connected LSCP server
141     * asynchronously.
142     *
143     * @param s - sequence of bytes (string) to be sent
144     */
145 schoenebeck 2515 bool LSCPClient::send(String s) {
146 schoenebeck 2535 //lscpLog("[send] '%s'\n", s.c_str());
147 schoenebeck 2515 if (!isConnected()) return false;
148 schoenebeck 3054 int n = (int) ::write(hSocket, &s[0], s.size());
149 schoenebeck 2515 return n == s.size();
150     }
151    
152 schoenebeck 2535 /**
153     * Send a sequence of bytes given by @a s to the connected LSCP server
154     * synchronously. This method will then block until a response line was
155     * received from the LSCP server and will return that response line as result
156     * to this method. Since this currently assumes a single line response, it
157     * should just be used for sending LSCP commands which will exepct a single
158     * line response from the LSCP server.
159     *
160     * @param s - sequence of bytes (string) to be sent
161     * @returns response of LSCP server
162     */
163 schoenebeck 2515 String LSCPClient::sendCommandSync(String s) {
164     m_linesMutex.Lock();
165     m_lines.clear();
166     m_linesMutex.Unlock();
167    
168     m_sync.Set(true);
169     if (!send(s + "\n")) return "";
170     m_sync.WaitIf(true);
171    
172     m_linesMutex.Lock();
173 schoenebeck 2535 String sResponse = m_lines.back().data;
174 schoenebeck 2515 m_lines.clear();
175     m_linesMutex.Unlock();
176    
177     return sResponse;
178     }
179    
180 schoenebeck 2535 /**
181     * Pulls out and returns the next line received from the LSCP server. If the
182     * return value of this method evaluates to false, then there is currently no
183     * new line received. The line returned will be removed from the internal
184     * receive buffer. If that's not what you want, then use lookAheadLine()
185     * instead.
186     */
187 schoenebeck 2515 optional<String> LSCPClient::popLine() {
188     String s;
189     LockGuard guard(m_linesMutex);
190     if (m_lines.empty()) return optional<String>::nothing;
191 schoenebeck 2535 s = m_lines.front().data;
192 schoenebeck 2515 m_lines.pop_front();
193     return s;
194     }
195    
196 schoenebeck 2535 /**
197     * Use this method instead of popLine() in case you want to access received
198     * line(s) without removing them from the internal buffer.
199     *
200     * @param index - offset of line to be accessed (0 being the oldest line
201     * received)
202     */
203 schoenebeck 2515 optional<String> LSCPClient::lookAheadLine(int index) {
204     LockGuard guard(m_linesMutex);
205     if (index < 0 || index >= m_lines.size())
206     return optional<String>::nothing;
207 schoenebeck 2535 std::list<Line>::iterator it = m_lines.begin();
208 schoenebeck 2515 for (int i = 0; i < index; ++i) ++it;
209 schoenebeck 2535 String s = (*it).data;
210 schoenebeck 2515 return s;
211     }
212    
213 schoenebeck 2535 /**
214     * Returns true if the received line(s), waiting to be pulled out with
215     * popLine(), is/are completely received. So if the next line waiting to be
216     * pulled out with popLine() is a single line response, then this function
217     * returns true. If the next line waiting to be pulled out with popLine() is
218     * considered a part of multi line response though, this function only returns
219     * true if all lines of that expected multi line response were entirely
220     * received already.
221     *
222     * @see multiLine()
223     */
224 schoenebeck 2515 bool LSCPClient::messageComplete() {
225     LockGuard guard(m_linesMutex);
226 schoenebeck 2535 if (m_lines.empty()) {
227     //lscpLog("\t[messageComplete false - buffer empty]\n");
228     return false;
229     }
230     if (!m_lines.front().isMultiLine) {
231     //lscpLog("\t[messageComplete true - single line]\n");
232     return true;
233     }
234    
235     // // just for debugging purposes ...
236     // int k = 0;
237     // for (std::list<Line>::const_iterator it = m_lines.begin();
238     // it != m_lines.end(); ++it, ++k)
239     // {
240     // lscpLog("\t[messageComplete k=%d : mul=%d cmpl=%d data='%s']\n", k, (*it).isMultiLine, (*it).isMultiLineComplete, (*it).data.c_str());
241     // }
242    
243     // so next line is part of a multi line response, check if it's complete ...
244     for (std::list<Line>::const_iterator it = m_lines.begin();
245     it != m_lines.end(); ++it)
246     {
247     if ((*it).isMultiLineComplete) return true;
248     }
249     return false;
250 schoenebeck 2515 }
251    
252 schoenebeck 2535 /**
253     * Returns true if the next line to be pulled out with popLine() is considered
254     * a single line response, it returns false if the next line is a part of a
255     * multi line response instead. In the latter case you should call multiLine()
256     * to check whether all lines of that multi line response were already received
257     * before pulling them out with popLine().
258     *
259     * @see messageComplete()
260     */
261 schoenebeck 2515 bool LSCPClient::multiLine() {
262 schoenebeck 2535 LockGuard guard(m_linesMutex);
263     if (m_lines.empty()) return false;
264     bool bIsMultiLine = m_lines.front().isMultiLine;
265     return bIsMultiLine;
266 schoenebeck 2515 }
267    
268 schoenebeck 2535 /**
269     * Returns true if new line(s) were received from LSCP server, waiting to be
270     * pulled out with popLine().
271     */
272 schoenebeck 2515 bool LSCPClient::lineAvailable() const {
273     return !m_lines.empty(); // is thread safe
274     }
275    
276 schoenebeck 2535 /**
277     * You may call this method to register a callback function which shall be
278     * notified if new data was received from the connected LSCP server.
279     */
280 schoenebeck 2515 void LSCPClient::setCallback(Callback_t fn) {
281     m_callback = fn;
282     }
283    
284 schoenebeck 2535 /**
285     * You may call this method to register a callback function which shall be
286     * notified if some kind of network error occurred.
287     */
288 schoenebeck 2532 void LSCPClient::setErrorCallback(Callback_t fn) {
289     m_errorCallback = fn;
290     }
291    
292 schoenebeck 2535 /**
293     * This method is only called by the internal client thread: a call to this
294     * method blocks until a complete new line was received from the LSCP server.
295     */
296 schoenebeck 2515 optional<String> LSCPClient::receiveLine() {
297     if (!isConnected()) return optional<String>::nothing;
298     for (char c; true; ) {
299 schoenebeck 3054 int n = (int) ::read(hSocket, &c, 1);
300 schoenebeck 2515 if (n < 1) return optional<String>::nothing;
301     if (c == '\r') continue;
302     if (c == '\n') {
303     String s = m_lineBuffer;
304     m_lineBuffer.clear();
305     return s;
306     }
307     //printf("->%c\n", c);
308     m_lineBuffer += c;
309     }
310     return optional<String>::nothing;
311     }
312    
313 schoenebeck 2535 /**
314     * Client's internal receiving thread's loop. It does nothing else than waiting
315     * for new data sent by LSCP server and puts the received lines into an
316     * internal FIFO buffer.
317     */
318 schoenebeck 2515 int LSCPClient::Main() {
319 schoenebeck 3766 #if DEBUG
320     Thread::setNameOfCaller("LSCPClient");
321     #endif
322    
323 schoenebeck 2515 while (true) {
324     optional<String> pLine = receiveLine();
325 schoenebeck 3766
326     // prevent thread from being cancelled
327     // (e.g. to prevent deadlocks while holding mutex lock(s))
328     pushCancelable(false);
329    
330 schoenebeck 2535 if (pLine) { // if there was a line received ...
331     //lscpLog("[client receiveLine] '%s'\n", pLine->c_str());
332 schoenebeck 2515 String s = *pLine;
333 schoenebeck 2535
334     // check whether this is a multi line data ...
335     bool bSkipLine = false;
336     bool bMultiLineComplete = false;
337     if (!m_multiLineExpected && _lscpLineIndicatesMultiLineResponse(s, bSkipLine)) {
338 schoenebeck 2515 m_multiLineExpected = true;
339 schoenebeck 2535 } else if (m_multiLineExpected && s.substr(0, 1) == ".") {
340     bMultiLineComplete = true;
341 schoenebeck 2515 }
342    
343 schoenebeck 2535 // store received line in internal FIFO buffer
344     if (!bSkipLine) {
345     Line l = { s, m_multiLineExpected, bMultiLineComplete };
346     m_linesMutex.Lock();
347     m_lines.push_back(l);
348     m_linesMutex.Unlock();
349     }
350    
351     // if this was the last line of a multi line response, reset
352     // for next run
353     if (m_multiLineExpected && bMultiLineComplete) {
354     m_multiLineExpected = false;
355     }
356    
357 schoenebeck 2515 if (m_sync.GetUnsafe()) m_sync.Set(false);
358     else if (m_callback) (*m_callback)(this);
359 schoenebeck 2532 } else if (m_errorCallback) (*m_errorCallback)(this);
360 schoenebeck 3766
361     // now allow thread being cancelled again
362     // (since all mutexes are now unlocked)
363     popCancelable();
364    
365 schoenebeck 2515 TestCancel();
366     }
367     return 0; // just to avoid a warning with some old compilers
368     }

  ViewVC Help
Powered by ViewVC