Merge ~dirk.zimoch/epics-base:iocLogClientFixesTry2 into ~epics-core/epics-base/+git/epics-base:7.0

Proposed by Dirk Zimoch
Status: Merged
Merged at revision: e178fa85b6db1c798dad3fb8310652aaa2110567
Proposed branch: ~dirk.zimoch/epics-base:iocLogClientFixesTry2
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Diff against target: 708 lines (+238/-153)
11 files modified
modules/database/src/ioc/misc/dbCore.dbd (+3/-0)
modules/libcom/src/log/iocLog.c (+25/-0)
modules/libcom/src/log/logClient.c (+103/-152)
modules/libcom/src/log/logClient.h (+0/-1)
modules/libcom/src/osi/Makefile (+1/-0)
modules/libcom/src/osi/os/Darwin/osdSockUnsentCount.c (+19/-0)
modules/libcom/src/osi/os/Linux/osdSockUnsentCount.c (+19/-0)
modules/libcom/src/osi/os/WIN32/osdSockUnsentCount.c (+26/-0)
modules/libcom/src/osi/os/default/osdSockUnsentCount.c (+15/-0)
modules/libcom/src/osi/os/iOS/osdSockUnsentCount.c (+19/-0)
modules/libcom/src/osi/osiSock.h (+8/-0)
Reviewer Review Type Date Requested Status
mdavidsaver Needs Fixing
Review via email: mp+372925@code.launchpad.net

This proposal supersedes a proposal from 2019-08-30.

Commit message

fix several problems in logClient that lead to lost messages when the server (or firewall in between) disconnects

Description of the change

Bugfix: don't send errlog to all logClients, only to iocLog
Improvements to minimize message loss when server disconnects:
* detect lost connection to server early
* do not discard buffer at disconnect
* flush buffer directly after reconnect
Other improvements:
* avoid delays on ioc startup and shutdown
* avoid repeated memmove

Relates to bug report https://bugs.launchpad.net/epics-base/+bug/1841608 and Tech-Talk thread https://epics.anl.gov/tech-talk/2019/msg01329.php.

To post a comment you must log in.
Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

What necessitates removing shutdown() and adding select()/recv()? To my knowledge send() should return with an error if the socket is, or becomes, disconnected.

review: Needs Information
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

Unfortunately send() does not return an error immediately. It simply puts the message into the TCP send queue, even when the peer has already shut down the socket. Only one or more messages later send() returns an error. But by then the messages are lost. The logClient has already "successfully" sent them ti the TCP stack but TCP failed to deliver them and there is no way to recover the messages now.

From the send man page: "No indication of failure to deliver is implicit in a send()."
It only tells you that queuing the message was successful.

When the peer closes the socket, select() will show the socket readable and read() will return 0, but only after everything the peer may have actually send has been read (and discarded).
That is the only reliable way to detect a closed connection.
However this method only works if the receiving side had not been shut down.

I had to google a lot until I found this out. It seems many people get this wrong assuming send() should fail immediately. It doesn't.

I can't find the page that helped me in the end any more, but this one comes close:
https://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-2.html

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

When say "closed connection" do you mean normal closure (FIN/ACK), abnormal closure (RST), and/or (ideally) timeout?

Lack of an application level timeout is a known issue with the logging protocol. Are you trying to address this too?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

I don't know the TCP details. I tested with a local server (ncat) which I terminated and also with our two production servers (syslog-ng adnd logstash) where the firewall closes idle connections after one hour. In both cases I can now detect when the connection breaks. Before I always lost messages.

Which timeout scenario do you have in mind? send() hanging? How could I test that?

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

I notice that the page you link states:

> DISCLAIMER: This is my interpretation of the RFCs (I have read all the TCP-related ones I could find), but I have not attempted to examine implementation source code or trace actual connections in order to verify it. I am satisfied that the logic is correct, though.

which makes me smile.

The situation you describe, with a "firewall" in between is actually fairly complicated. And opaque in that the firewall might be acting in different ways depending on how stateful and helpful it is (eg. whether one or both sides get RST).

What I understand about BSD sockets and TCP is that:

1. If the peer maintains the connection, but doesn't call recv() (at all, or not fast enough) then send() will eventually fill the Tx socket buffer FIFO and block until it becomes not full (by peer ACK on recv() ).

2. calling send() on a socket which has been closed (peer calls close()) while the TX FIFO is empty results in an immediate EPIPE.

3. if a send() is in progress because the Tx FIFO is full (or probably when not empty), a peer close results in ECONNRESET.

4. if a the peer has shutdown(SHUT_RD), then send() will succeed until the Tx FIFO is full, then it will block as with #2. When the peer does close(), the send will return with EPIPE. (or probably ECONNRESET if some data was sent, but not ACK'd before the FIN).

5. if the peer simply stops responding, then send() will succeed until the Tx FIFO is full, then block waiting for an ACK which never comes.

So normal, or abnormal, termination of the TCP connection will break a send() immediately after RST or the FIN+ACK handshake completes. peer shutdown(SHUT_RD) or interruption of the network path will not break or error send().

I've just verified 1,2,3,4 on Linux with short python scripts. (I was surprised that #4 doesn't error) #5 I know from past experience.

So I don't think that calling recv() serves the purpose you intend. I think that SO_SNDTIMEO would be a better fit.

That said, the recv() might serve as one piece of a mechanism for a log server to detect condition #5 faster than SO_KEEPALIVE would (imo. SO_KEEPALIVE should also be set). Some extra protocol would be needed to allow a log server to detect this protocol feature.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

4. Was the problem. send does not fail until some time later. But than messages are already lost.
So my fix is not to rely on send failing but on recv returning EOF.
SO_KEEPALIVE has a default timeout of 2 hours and you cannot change it per socket but only system wide. It is totally useless for solving the problems with lost messages. It is there to allow to clean up stale connections and not to run out of file descriptors in server programs that are constantly running. But that's about all. So it may help the log server but not the client.

The problem I solve here is that send succeeds until the FIFO is full, just as you pointed out in 4. and 5.

I cannot confirm 2 though. My tests with ncat as a server have shown that send does not detect immediately when the server has exited (and thus the kernel has closed the socket).

But leaving discussions about how TCP is supposed to work aside, my main argument for this change is: Now it works, before it didn't.

Revision history for this message
Andrew Johnson (anj) wrote : Posted in a previous version of this proposal

@Dirk: Does your new version still work with the original iocLogServer?

IIRC at connection time that server explicitly closes the socket in the log-server -> client IOC direction because the server never sends data back to the client. I'm not sure whether that might cause the client to get an EOF from a recv() call sooner than with the other log-servers that don't do that close.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

Indeed this looks like a close to the client and it closes the connection, but re-opens it on the next message. No messages are lost. The disconnect is not nice though. I'll have a look how to fix that.

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

wrt. iocLogServer, I think it would be reasonable to accept removal of the shutdown(SHUT_WR) in iocLogServer.c. So long as older IOCs can still log against newer iocLogServer (which I think would be true).

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

It seems that the broken connection is quite reliably detected at the second send() after the connection had been closed. A dummy send(socket, NULL, 0, 0) seems sufficient to detect the broken connection to iocLogServer as well as to ncat and do Michael's server.py example.

Revision history for this message
Andrew Johnson (anj) wrote : Posted in a previous version of this proposal

@Dirk: Which architectures have you tested that on?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

Linux so far. I am about to test on vxWorks and Windows right now.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

Hmm... Not all Linux versions behave the same. On some send(socket, NULL, 0, 0) is a NOOP and does not detect a broken connection.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

The send() trick with 0 bytes does not work on all systems. It seems to depend on the Linux kernel version as well.

I am testing now a different solution. On Linux the call ioctl(sock, TIOCOUTQ, &unsent) gives the number of unsent bytes in the TCP output queue. This allows to keep the unsent bytes in the buffer in case it turns out later that the connection is broken.

On macOS there seems to be a similar functionality with getsockopt(sock, SOL_SOCKET, SO_NWRITE, &unsent), but I cannot test because I have no mac.

Other Unix flavors may or may not have such a feature.

For vxWorks I could not find a way to get the number of unsent bytes, neither for version 5.5 nor for 6.7.

I don't know about RTEMS.

On Windows 10 the number of unsent bytes should be available with WSAIoctl(sock, SIO_TCP_INFO, ..._). I spent most of last week to find out why it is not working before finding out that the Win 10 version I got was too old. I am upgrading now. Older Windows versions seem to have no way to find out the number of unsent bytes.

So I can mitigate the problem of message loss for some clients but not for all. Depending on the OS.

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

> The send() trick with 0 bytes does not work on all systems.

Unless poll() is used, there is a race condition involved. A second send() will only fail if a RST has been received. This may or may not be platform dependent.

Have you discarded the idea of removing the 'shutdown(SHUT_WR)' from iocLogServer?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

I first want to stay as compatible as possible to existing iocLogServer implementations.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

I made a new branch some commits below the head of this one. How can I change the merge request to the new branch?

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

I usually do a force push to replace the existing branch content. I don't think branches can be changed, so the alternative is a new MR.

Revision history for this message
Martin Konrad (info-martin-konrad) wrote : Posted in a previous version of this proposal

Beware that force pushing messes up the diff on Launchpad's MR. Dirk, have you considered doing this in smaller steps (separate MRs for each issue you are addressing)? This way some of your modifications might get merged faster.

Revision history for this message
Ralph Lange (ralph-lange) wrote : Posted in a previous version of this proposal

iirc, when you properly supersede the MP through the web interface, you have an option to pull/link the existing conversation to the new proposal.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

The "resubmit proposal" link seems to do the job. It allows to switch branches. No force push needed. Diffs look fine and the conversation gets transferred.
Martin, the smaller steps are the individual commits. Each should be one (and in most cases small and understandable) change. Each commit I did results in working code. So if there is a problem with one particular step, it can be reverted.

I am not yet quite satisfied with the behavior, so some more changes may come. But please have a look already.

My test:
Start iocLogServer
Start softIoc and do ioclogInit
errlog msg1
Stop iocLogServer and restart it
errlog msg2
wait at least 10 seconds
errlog msg3

All 3 error messages should be logged.
On Linux (all version?) connection loss is detected latest 5 seconds after msg2.
On recent Win 10 connection loss is detected at only at msg3 because send(sock,NULL,0) has no effect,
 but no message is lost.
On vxWorks 5.5 and older Windows versions (< Win 10 version 1703) msg2 is lost because they have no o way to detect when a message is actually sent on the network.
Can someone test with Mac and RTEMS?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote : Posted in a previous version of this proposal

So, now I have both variants for discussion.
This one, which requires a change in iocLogServer as well (not to shutdown its write channel).
Or mp+372925 which does not quite work for vxWorks and older Windows and uses an unspecified feature of send() in Linux.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> Stop iocLogServer and restart it

A related test would be to suspend (SIGSTOP) iocLogServer. This way the socket remains allocated, which is a better simulation of a non-responsive peer.

You might also want to try sending (large) log messages until the various queues fill up. Messages will be lost at some point.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I did that briefly. The client (ioc) happily sends to the server and on the server the TCP input queue fills up. If the server then dies the messages are lost.

I have not tried it until the queues filled up. The the logClient may block while holding the mutex which may make errlog (or whatever client) block. I will test that.

I suggest to remove the code that attempts to send from sendMessageChunk() and accept (and report) that messages are lost.

Can you please also review the usage of the mutex in the original code. I have the impression it does not really make sense.

Revision history for this message
Andrew Johnson (anj) wrote :

Can the necessary testing be done in modules/libcom/test/epicsErrlogTest.c or something similar? That file already has tests that implement a local log-server, needed to check iocLogPrefix().

Dirk, did you check this against a VxWorks 6.x version using the newer IP stack? We aren't supporting VxWorks 5.5 in EPICS 7 any more, but we do want it to work with the newer OS versions.

Some comments against the diff as well, but note that I haven't reviewed the functionality, just the build and APIs.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

epicsErrlogTest.c: I have not yet looked into this but I guess it should be possible.

VxWorks 6: I don't have vxWorks 6 running on my IOCs. Was always low on my work list.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

'#if defined (TIOCOUTQ)' fails when compiling for vxWorks 6. The macro does not exist in any vxWorks 6 header file. Same for SO_NWRITE. What exists is FIONWRITE, but that does not work on sockes, only on serial lines (at least for vxWorks 5).

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Hm, the in-diff comments become kind of hidden with a new push.
About epicsSocketCountUnsentBytes() [or whatever we call it] returning size_t instead of int: In vxWorks and Windows all socket operations use int for size and Windows does not have ssize_t.

Revision history for this message
Andrew Johnson (anj) wrote :

I see what you mean about viewing in-diff comments. Clicking one of the "Show diff comments" links in an existing comment helped me earlier, but older comments seem to have disappeared (at least while I have some new unsaved diff comments pending).

I withdraw my size_t suggestion, but see my new in-diff comments regarding the name.

Revision history for this message
Dirk Zimoch (dirk.zimoch) :
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

> older comments seem to have disappeared (at least while I have some new unsaved diff
> comments pending).

That may be the result a result of force-push when an in-diff comment refers to a line in a commit that has disappeared.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Dirk, I hope you won't take this as a disincentive, but I think a better approach would be either to switch to an actually reliable logging protocol, log over TLS with the heartbeat extension, switch to simply UDP logging, and/or teach caPutLog to use the system logging (aka call 'syslog()' or open '/dev/log').

I happened upon https://rainer.gerhards.net/2008/04/on-unreliability-of-plain-tcp-syslog.html which does a good job of summarizing some of the thoughts I've been having about iocLog "protocol" and the seeming impossibility of making it truly reliable while maintaining compatibility.

I also wonder if it would be better to move iocLogClient out of Base and into the caPutLog module as an internal API? caPutLog seems to be the only (public) user of this API, and I think that removing the separation would enable a more effective restructuring of the code.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Specifically, I don't like the idea of epicsSocketCountUnsentBytes() becoming public API. This is a highly specific use case, which can't be fully supported on our range of target OSs. I would like to see it removed from the public/installed headers unless at least one external user can be identified.

review: Needs Fixing
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I think the 'send(NULL)' pattern is a race condition as it depends on a TCP reset message having arrived prior to the second send(NULL) call. With real network latency I don't think it will work as intended to quickly and reliably detect when the preceding send() elicits a RST.

I'm not going to insist on a change here because I don't think it will break anything. The RST just won't be handled (via. EPIPE) unless the next time the log queue is flushed, and send() is called again.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

cf. https://code.launchpad.net/~epics-core/epics-base/+git/Com/+merge/373098

which is not mutually exclusive to this work. I wanted to try the idea.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

It is totally fine for me to keep os-specific unsent cound inside logClient as a bunch of #if branches.

'send(NULL)' is not race condition, because I do not rely on getting the RST right after sending. It is fine to get it at some point. It simply polls for RST every 5 seconds.

Maybe it is better to use the other approach: https://code.launchpad.net/~dirk.zimoch/epics-base/+git/epics-base/+merge/372043. This one is very os-dependent and uses unspecified behavior.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Results of discussion at core dev. meeting

Add '#ifdef EPICS_PRIVATE_API' around declaration of epicsSocketCountUnsentBytes() in osiSock.h.

Also, can you confirm that you've tested this (final) version on a target where epicsSocketCountUnsentBytes()==-1 ?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I have tested it against vxWorks 5 where epicsSocketCountUnsentBytes()==-1. The effect is that the log Client cannot keep track of the unsent bytes and messages may be lost as before.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

I did a test merge. Some CI failures resulted with cross-mingw and some older MSVC. Looks like some of the defines and structs related to TCP_INFO aren't available.

https://ci.appveyor.com/project/mdavidsaver/epics-base/builds/28358563

https://travis-ci.org/mdavidsaver/epics-base/builds/602443942

review: Needs Fixing
Revision history for this message
mdavidsaver (mdavidsaver) wrote :
Revision history for this message
Andrew Johnson (anj) wrote :

This could use an entry describing the changes in the Release Notes (which are now formatted in Markdown on both branches; I merged 3.15 up over the weekend and just pushed).

Revision history for this message
mdavidsaver (mdavidsaver) wrote : Posted in a previous version of this proposal

The other variant was merged, so this one is no longer relevant.

review: Disapprove

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/modules/database/src/ioc/misc/dbCore.dbd b/modules/database/src/ioc/misc/dbCore.dbd
index 51f9c96..adde24e 100644
--- a/modules/database/src/ioc/misc/dbCore.dbd
+++ b/modules/database/src/ioc/misc/dbCore.dbd
@@ -33,3 +33,6 @@ variable(callbackParallelThreadsDefault,int)
3333
34# Real-time operation34# Real-time operation
35variable(dbThreadRealtimeLock,int)35variable(dbThreadRealtimeLock,int)
36
37# show logClient network activity
38variable(logClientDebug,int)
diff --git a/modules/libcom/src/log/iocLog.c b/modules/libcom/src/log/iocLog.c
index e62da20..ba78041 100644
--- a/modules/libcom/src/log/iocLog.c
+++ b/modules/libcom/src/log/iocLog.c
@@ -18,8 +18,10 @@
1818
19#define epicsExportSharedSymbols19#define epicsExportSharedSymbols
20#include "envDefs.h"20#include "envDefs.h"
21#include "errlog.h"
21#include "logClient.h"22#include "logClient.h"
22#include "iocLog.h"23#include "iocLog.h"
24#include "epicsExit.h"
2325
24int iocLogDisable = 0;26int iocLogDisable = 0;
2527
@@ -75,6 +77,24 @@ void epicsShareAPI epicsShareAPI iocLogFlush (void)
75}77}
7678
77/*79/*
80 * logClientSendMessage ()
81 */
82static void logClientSendMessage ( logClientId id, const char * message )
83{
84 if ( !iocLogDisable ) {
85 logClientSend (id, message);
86 }
87}
88
89/*
90 * iocLogClientDestroy()
91 */
92static void iocLogClientDestroy (logClientId id)
93{
94 errlogRemoveListeners (logClientSendMessage, id);
95}
96
97/*
78 * iocLogClientInit()98 * iocLogClientInit()
79 */99 */
80static logClientId iocLogClientInit (void)100static logClientId iocLogClientInit (void)
@@ -89,6 +109,10 @@ static logClientId iocLogClientInit (void)
89 return NULL;109 return NULL;
90 }110 }
91 id = logClientCreate (addr, port);111 id = logClientCreate (addr, port);
112 if (id != NULL) {
113 errlogAddListener (logClientSendMessage, id);
114 epicsAtExit (iocLogClientDestroy, id);
115 }
92 return id;116 return id;
93}117}
94118
@@ -135,3 +159,4 @@ logClientId epicsShareAPI logClientInit (void)
135{159{
136 return iocLogClientInit ();160 return iocLogClientInit ();
137}161}
162
diff --git a/modules/libcom/src/log/logClient.c b/modules/libcom/src/log/logClient.c
index 99ee671..9a09ef7 100644
--- a/modules/libcom/src/log/logClient.c
+++ b/modules/libcom/src/log/logClient.c
@@ -21,11 +21,11 @@
21#include <string.h>21#include <string.h>
22#include <stdio.h>22#include <stdio.h>
2323
24#define EPICS_PRIVATE_API
24#define epicsExportSharedSymbols25#define epicsExportSharedSymbols
25#include "dbDefs.h"26#include "dbDefs.h"
26#include "epicsEvent.h"27#include "epicsEvent.h"
27#include "iocLog.h"28#include "iocLog.h"
28#include "errlog.h"
29#include "epicsMutex.h"29#include "epicsMutex.h"
30#include "epicsThread.h"30#include "epicsThread.h"
31#include "epicsTime.h"31#include "epicsTime.h"
@@ -33,9 +33,13 @@
33#include "epicsAssert.h"33#include "epicsAssert.h"
34#include "epicsExit.h"34#include "epicsExit.h"
35#include "epicsSignal.h"35#include "epicsSignal.h"
36#include "epicsExport.h"
3637
37#include "logClient.h"38#include "logClient.h"
3839
40int logClientDebug = 0;
41epicsExportAddress (int, logClientDebug);
42
39typedef struct {43typedef struct {
40 char msgBuf[0x4000];44 char msgBuf[0x4000];
41 struct sockaddr_in addr;45 struct sockaddr_in addr;
@@ -44,8 +48,10 @@ typedef struct {
44 SOCKET sock;48 SOCKET sock;
45 epicsThreadId restartThreadId;49 epicsThreadId restartThreadId;
46 epicsEventId stateChangeNotify;50 epicsEventId stateChangeNotify;
51 epicsEventId shutdownNotify;
47 unsigned connectCount;52 unsigned connectCount;
48 unsigned nextMsgIndex;53 unsigned nextMsgIndex;
54 unsigned backlog;
49 unsigned connected;55 unsigned connected;
50 unsigned shutdown;56 unsigned shutdown;
51 unsigned shutdownConfirm;57 unsigned shutdownConfirm;
@@ -53,7 +59,6 @@ typedef struct {
53} logClient;59} logClient;
5460
55static const double LOG_RESTART_DELAY = 5.0; /* sec */61static const double LOG_RESTART_DELAY = 5.0; /* sec */
56static const double LOG_SERVER_CREATE_CONNECT_SYNC_TIMEOUT = 5.0; /* sec */
57static const double LOG_SERVER_SHUTDOWN_TIMEOUT = 30.0; /* sec */62static const double LOG_SERVER_SHUTDOWN_TIMEOUT = 30.0; /* sec */
5863
59/*64/*
@@ -66,10 +71,10 @@ static char* logClientPrefix = NULL;
66 */71 */
67static void logClientClose ( logClient *pClient )72static void logClientClose ( logClient *pClient )
68{73{
69# ifdef DEBUG74 if (logClientDebug) {
70 fprintf (stderr, "log client: lingering for connection close...");75 fprintf (stderr, "log client: lingering for connection close...");
71 fflush (stderr);76 fflush (stderr);
72# endif77 }
7378
74 /*79 /*
75 * mutex on80 * mutex on
@@ -84,8 +89,6 @@ static void logClientClose ( logClient *pClient )
84 pClient->sock = INVALID_SOCKET;89 pClient->sock = INVALID_SOCKET;
85 }90 }
8691
87 pClient->nextMsgIndex = 0u;
88 memset ( pClient->msgBuf, '\0', sizeof ( pClient->msgBuf ) );
89 pClient->connected = 0u;92 pClient->connected = 0u;
9093
91 /*94 /*
@@ -93,9 +96,8 @@ static void logClientClose ( logClient *pClient )
93 */96 */
94 epicsMutexUnlock (pClient->mutex);97 epicsMutexUnlock (pClient->mutex);
9598
96# ifdef DEBUG99 if (logClientDebug)
97 fprintf (stderr, "done\n");100 fprintf (stderr, "done\n");
98# endif
99}101}
100102
101/*103/*
@@ -113,6 +115,7 @@ static void logClientDestroy (logClientId id)
113 epicsMutexMustLock ( pClient->mutex );115 epicsMutexMustLock ( pClient->mutex );
114 pClient->shutdown = 1u;116 pClient->shutdown = 1u;
115 epicsMutexUnlock ( pClient->mutex );117 epicsMutexUnlock ( pClient->mutex );
118 epicsEventSignal ( pClient->shutdownNotify );
116119
117 /* unblock log client thread blocking in send() or connect() */120 /* unblock log client thread blocking in send() or connect() */
118 interruptInfo =121 interruptInfo =
@@ -154,13 +157,11 @@ static void logClientDestroy (logClientId id)
154 return;157 return;
155 }158 }
156159
157 errlogRemoveListeners ( logClientSendMessage, (void *) pClient );
158
159 logClientClose ( pClient );160 logClientClose ( pClient );
160161
161 epicsMutexDestroy ( pClient->mutex );162 epicsMutexDestroy ( pClient->mutex );
162
163 epicsEventDestroy ( pClient->stateChangeNotify );163 epicsEventDestroy ( pClient->stateChangeNotify );
164 epicsEventDestroy ( pClient->shutdownNotify );
164165
165 free ( pClient );166 free ( pClient );
166}167}
@@ -176,61 +177,26 @@ static void sendMessageChunk(logClient * pClient, const char * message) {
176 unsigned msgBufBytesLeft = 177 unsigned msgBufBytesLeft =
177 sizeof ( pClient->msgBuf ) - pClient->nextMsgIndex;178 sizeof ( pClient->msgBuf ) - pClient->nextMsgIndex;
178179
179 if ( strSize > msgBufBytesLeft ) {180 if ( msgBufBytesLeft < strSize && pClient->nextMsgIndex != 0u && pClient->connected)
180 int status;181 {
181182 /* buffer is full, thus flush it */
182 if ( ! pClient->connected ) {183 logClientFlush ( pClient );
183 break;184 msgBufBytesLeft = sizeof ( pClient->msgBuf ) - pClient->nextMsgIndex;
184 }
185
186 if ( msgBufBytesLeft > 0u ) {
187 memcpy ( & pClient->msgBuf[pClient->nextMsgIndex],
188 message, msgBufBytesLeft );
189 pClient->nextMsgIndex += msgBufBytesLeft;
190 strSize -= msgBufBytesLeft;
191 message += msgBufBytesLeft;
192 }
193
194 status = send ( pClient->sock, pClient->msgBuf,
195 pClient->nextMsgIndex, 0 );
196 if ( status > 0 ) {
197 unsigned nSent = (unsigned) status;
198 if ( nSent < pClient->nextMsgIndex ) {
199 unsigned newNextMsgIndex = pClient->nextMsgIndex - nSent;
200 memmove ( pClient->msgBuf, & pClient->msgBuf[nSent],
201 newNextMsgIndex );
202 pClient->nextMsgIndex = newNextMsgIndex;
203 }
204 else {
205 pClient->nextMsgIndex = 0u;
206 }
207 }
208 else {
209 if ( ! pClient->shutdown ) {
210 char sockErrBuf[64];
211 if ( status ) {
212 epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
213 }
214 else {
215 strcpy ( sockErrBuf, "server initiated disconnect" );
216 }
217 fprintf ( stderr, "log client: lost contact with log server at \"%s\" because \"%s\"\n",
218 pClient->name, sockErrBuf );
219 }
220 logClientClose ( pClient );
221 break;
222 }
223 }185 }
224 else {186 if ( msgBufBytesLeft == 0u ) {
225 memcpy ( & pClient->msgBuf[pClient->nextMsgIndex],187 fprintf ( stderr, "log client: messages to \"%s\" are lost\n",
226 message, strSize );188 pClient->name );
227 pClient->nextMsgIndex += strSize;
228 break;189 break;
229 }190 }
191 if ( msgBufBytesLeft > strSize) msgBufBytesLeft = strSize;
192 memcpy ( & pClient->msgBuf[pClient->nextMsgIndex],
193 message, msgBufBytesLeft );
194 pClient->nextMsgIndex += msgBufBytesLeft;
195 strSize -= msgBufBytesLeft;
196 message += msgBufBytesLeft;
230 }197 }
231}198}
232199
233
234/* 200/*
235 * logClientSend ()201 * logClientSend ()
236 */202 */
@@ -255,43 +221,54 @@ void epicsShareAPI logClientSend ( logClientId id, const char * message )
255221
256void epicsShareAPI logClientFlush ( logClientId id )222void epicsShareAPI logClientFlush ( logClientId id )
257{223{
224 unsigned nSent;
225 int status = 0;
226
258 logClient * pClient = ( logClient * ) id;227 logClient * pClient = ( logClient * ) id;
259228
260 if ( ! pClient ) {229 if ( ! pClient || ! pClient->connected ) {
261 return;230 return;
262 }231 }
263232
264 epicsMutexMustLock ( pClient->mutex );233 epicsMutexMustLock ( pClient->mutex );
265234
266 while ( pClient->nextMsgIndex && pClient->connected ) {235 nSent = pClient->backlog;
267 int status = send ( pClient->sock, pClient->msgBuf, 236 while ( nSent < pClient->nextMsgIndex && pClient->connected ) {
268 pClient->nextMsgIndex, 0 );237 status = send ( pClient->sock, pClient->msgBuf + nSent,
269 if ( status > 0 ) {238 pClient->nextMsgIndex - nSent, 0 );
270 unsigned nSent = (unsigned) status;239 if ( status < 0 ) break;
271 if ( nSent < pClient->nextMsgIndex ) {240 nSent += status;
272 unsigned newNextMsgIndex = pClient->nextMsgIndex - nSent;241 }
273 memmove ( pClient->msgBuf, & pClient->msgBuf[nSent], 242
274 newNextMsgIndex );243 if ( pClient->backlog > 0 && status >= 0 )
275 pClient->nextMsgIndex = newNextMsgIndex;244 {
276 }245 /* On Linux send 0 bytes can detect EPIPE */
277 else {246 /* NOOP on Windows, fails on vxWorks */
278 pClient->nextMsgIndex = 0u;247 errno = 0;
279 }248 status = send ( pClient->sock, NULL, 0, 0 );
249 if (!(errno == ECONNRESET || errno == EPIPE)) status = 0;
250 }
251
252 if ( status < 0 ) {
253 if ( ! pClient->shutdown ) {
254 char sockErrBuf[128];
255 epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
256 fprintf ( stderr, "log client: lost contact with log server at \"%s\" because \"%s\"\n",
257 pClient->name, sockErrBuf );
280 }258 }
281 else {259 pClient->backlog = 0;
282 if ( ! pClient->shutdown ) {260 logClientClose ( pClient );
283 char sockErrBuf[64];261 }
284 if ( status ) {262 else if ( nSent > 0 && pClient->nextMsgIndex > 0 ) {
285 epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );263 int backlog = epicsSocketUnsentCount ( pClient->sock );
286 }264 if (backlog >= 0) {
287 else {265 pClient->backlog = backlog;
288 strcpy ( sockErrBuf, "server initiated disconnect" );266 nSent -= backlog;
289 }267 }
290 fprintf ( stderr, "log client: lost contact with log server at \"%s\" because \"%s\"\n", 268 pClient->nextMsgIndex -= nSent;
291 pClient->name, sockErrBuf );269 if ( nSent > 0 && pClient->nextMsgIndex > 0 ) {
292 }270 memmove ( pClient->msgBuf, & pClient->msgBuf[nSent],
293 logClientClose ( pClient );271 pClient->nextMsgIndex );
294 break;
295 }272 }
296 }273 }
297 epicsMutexUnlock ( pClient->mutex );274 epicsMutexUnlock ( pClient->mutex );
@@ -302,10 +279,10 @@ void epicsShareAPI logClientFlush ( logClientId id )
302 */279 */
303static void logClientMakeSock (logClient *pClient)280static void logClientMakeSock (logClient *pClient)
304{281{
305 282 if (logClientDebug) {
306# ifdef DEBUG
307 fprintf (stderr, "log client: creating socket...");283 fprintf (stderr, "log client: creating socket...");
308# endif284 fflush (stderr);
285 }
309286
310 epicsMutexMustLock (pClient->mutex);287 epicsMutexMustLock (pClient->mutex);
311 288
@@ -314,7 +291,7 @@ static void logClientMakeSock (logClient *pClient)
314 */291 */
315 pClient->sock = epicsSocketCreate ( AF_INET, SOCK_STREAM, 0 );292 pClient->sock = epicsSocketCreate ( AF_INET, SOCK_STREAM, 0 );
316 if ( pClient->sock == INVALID_SOCKET ) {293 if ( pClient->sock == INVALID_SOCKET ) {
317 char sockErrBuf[64];294 char sockErrBuf[128];
318 epicsSocketConvertErrnoToString ( 295 epicsSocketConvertErrnoToString (
319 sockErrBuf, sizeof ( sockErrBuf ) );296 sockErrBuf, sizeof ( sockErrBuf ) );
320 fprintf ( stderr, "log client: no socket error %s\n", 297 fprintf ( stderr, "log client: no socket error %s\n",
@@ -323,10 +300,8 @@ static void logClientMakeSock (logClient *pClient)
323 300
324 epicsMutexUnlock (pClient->mutex);301 epicsMutexUnlock (pClient->mutex);
325302
326# ifdef DEBUG303 if (logClientDebug)
327 fprintf (stderr, "done\n");304 fprintf (stderr, "done\n");
328# endif
329
330}305}
331306
332/*307/*
@@ -366,7 +341,7 @@ static void logClientConnect (logClient *pClient)
366 }341 }
367 else {342 else {
368 if ( pClient->connFailStatus != errnoCpy && ! pClient->shutdown ) {343 if ( pClient->connFailStatus != errnoCpy && ! pClient->shutdown ) {
369 char sockErrBuf[64];344 char sockErrBuf[128];
370 epicsSocketConvertErrnoToString (345 epicsSocketConvertErrnoToString (
371 sockErrBuf, sizeof ( sockErrBuf ) );346 sockErrBuf, sizeof ( sockErrBuf ) );
372 fprintf (stderr,347 fprintf (stderr,
@@ -392,7 +367,7 @@ static void logClientConnect (logClient *pClient)
392 optval = TRUE;367 optval = TRUE;
393 status = setsockopt (pClient->sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&optval, sizeof(optval));368 status = setsockopt (pClient->sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&optval, sizeof(optval));
394 if (status<0) {369 if (status<0) {
395 char sockErrBuf[64];370 char sockErrBuf[128];
396 epicsSocketConvertErrnoToString ( 371 epicsSocketConvertErrnoToString (
397 sockErrBuf, sizeof ( sockErrBuf ) );372 sockErrBuf, sizeof ( sockErrBuf ) );
398 fprintf (stderr, "log client: unable to enable keepalive option because \"%s\"\n", sockErrBuf);373 fprintf (stderr, "log client: unable to enable keepalive option because \"%s\"\n", sockErrBuf);
@@ -404,11 +379,11 @@ static void logClientConnect (logClient *pClient)
404 */379 */
405 status = shutdown (pClient->sock, SHUT_RD);380 status = shutdown (pClient->sock, SHUT_RD);
406 if (status < 0) {381 if (status < 0) {
407 char sockErrBuf[64];382 char sockErrBuf[128];
408 epicsSocketConvertErrnoToString ( 383 epicsSocketConvertErrnoToString (
409 sockErrBuf, sizeof ( sockErrBuf ) );384 sockErrBuf, sizeof ( sockErrBuf ) );
410 fprintf (stderr, "%s:%d shutdown(%d,SHUT_RD) error was \"%s\"\n", 385 fprintf (stderr, "%s:%d shutdown(sock,SHUT_RD) error was \"%s\"\n",
411 __FILE__, __LINE__, pClient->sock, sockErrBuf);386 __FILE__, __LINE__, sockErrBuf);
412 /* not fatal (although it shouldn't happen) */387 /* not fatal (although it shouldn't happen) */
413 }388 }
414389
@@ -425,7 +400,7 @@ static void logClientConnect (logClient *pClient)
425 lingerval.l_linger = 60*5; 400 lingerval.l_linger = 60*5;
426 status = setsockopt (pClient->sock, SOL_SOCKET, SO_LINGER, (char *) &lingerval, sizeof(lingerval));401 status = setsockopt (pClient->sock, SOL_SOCKET, SO_LINGER, (char *) &lingerval, sizeof(lingerval));
427 if (status<0) {402 if (status<0) {
428 char sockErrBuf[64];403 char sockErrBuf[128];
429 epicsSocketConvertErrnoToString ( 404 epicsSocketConvertErrnoToString (
430 sockErrBuf, sizeof ( sockErrBuf ) );405 sockErrBuf, sizeof ( sockErrBuf ) );
431 fprintf (stderr, "log client: unable to set linger options because \"%s\"\n", sockErrBuf);406 fprintf (stderr, "log client: unable to set linger options because \"%s\"\n", sockErrBuf);
@@ -457,14 +432,10 @@ static void logClientRestart ( logClientId id )
457432
458 epicsMutexUnlock ( pClient->mutex );433 epicsMutexUnlock ( pClient->mutex );
459434
460 if ( isConn ) {435 if ( ! isConn ) logClientConnect ( pClient );
461 logClientFlush ( pClient );436 logClientFlush ( pClient );
462 }437
463 else {438 epicsEventWaitWithTimeout ( pClient->shutdownNotify, LOG_RESTART_DELAY);
464 logClientConnect ( pClient );
465 }
466
467 epicsThreadSleep ( LOG_RESTART_DELAY );
468439
469 epicsMutexMustLock ( pClient->mutex );440 epicsMutexMustLock ( pClient->mutex );
470 }441 }
@@ -480,9 +451,7 @@ static void logClientRestart ( logClientId id )
480logClientId epicsShareAPI logClientCreate (451logClientId epicsShareAPI logClientCreate (
481 struct in_addr server_addr, unsigned short server_port)452 struct in_addr server_addr, unsigned short server_port)
482{453{
483 epicsTimeStamp begin, current;
484 logClient *pClient;454 logClient *pClient;
485 double diff;
486455
487 pClient = calloc (1, sizeof (*pClient));456 pClient = calloc (1, sizeof (*pClient));
488 if (pClient==NULL) {457 if (pClient==NULL) {
@@ -507,14 +476,22 @@ logClientId epicsShareAPI logClientCreate (
507 pClient->shutdownConfirm = 0;476 pClient->shutdownConfirm = 0;
508477
509 epicsAtExit (logClientDestroy, (void*) pClient);478 epicsAtExit (logClientDestroy, (void*) pClient);
510 479
511 pClient->stateChangeNotify = epicsEventCreate (epicsEventEmpty);480 pClient->stateChangeNotify = epicsEventCreate (epicsEventEmpty);
512 if ( ! pClient->stateChangeNotify ) {481 if ( ! pClient->stateChangeNotify ) {
513 epicsMutexDestroy ( pClient->mutex );482 epicsMutexDestroy ( pClient->mutex );
514 free ( pClient );483 free ( pClient );
515 return NULL;484 return NULL;
516 }485 }
517 486
487 pClient->shutdownNotify = epicsEventCreate (epicsEventEmpty);
488 if ( ! pClient->shutdownNotify ) {
489 epicsMutexDestroy ( pClient->mutex );
490 epicsEventDestroy ( pClient->stateChangeNotify );
491 free ( pClient );
492 return NULL;
493 }
494
518 pClient->restartThreadId = epicsThreadCreate (495 pClient->restartThreadId = epicsThreadCreate (
519 "logRestart", epicsThreadPriorityLow, 496 "logRestart", epicsThreadPriorityLow,
520 epicsThreadGetStackSize(epicsThreadStackSmall),497 epicsThreadGetStackSize(epicsThreadStackSmall),
@@ -522,35 +499,12 @@ logClientId epicsShareAPI logClientCreate (
522 if ( pClient->restartThreadId == NULL ) {499 if ( pClient->restartThreadId == NULL ) {
523 epicsMutexDestroy ( pClient->mutex );500 epicsMutexDestroy ( pClient->mutex );
524 epicsEventDestroy ( pClient->stateChangeNotify );501 epicsEventDestroy ( pClient->stateChangeNotify );
502 epicsEventDestroy ( pClient->shutdownNotify );
525 free (pClient);503 free (pClient);
526 fprintf(stderr, "log client: unable to start log client connection watch dog thread\n");504 fprintf(stderr, "log client: unable to start log client connection watch dog thread\n");
527 return NULL;505 return NULL;
528 }506 }
529507
530 /*
531 * attempt to synchronize with circuit connect
532 */
533 epicsTimeGetCurrent ( & begin );
534 epicsMutexMustLock ( pClient->mutex );
535 do {
536 epicsMutexUnlock ( pClient->mutex );
537 epicsEventWaitWithTimeout (
538 pClient->stateChangeNotify,
539 LOG_SERVER_CREATE_CONNECT_SYNC_TIMEOUT / 10.0 );
540 epicsTimeGetCurrent ( & current );
541 diff = epicsTimeDiffInSeconds ( & current, & begin );
542 epicsMutexMustLock ( pClient->mutex );
543 }
544 while ( ! pClient->connected && diff < LOG_SERVER_CREATE_CONNECT_SYNC_TIMEOUT );
545 epicsMutexUnlock ( pClient->mutex );
546
547 if ( ! pClient->connected ) {
548 fprintf (stderr, "log client create: timed out synchronizing with circuit connect to \"%s\" after %.1f seconds\n",
549 pClient->name, LOG_SERVER_CREATE_CONNECT_SYNC_TIMEOUT );
550 }
551
552 errlogAddListener ( logClientSendMessage, (void *) pClient );
553
554 return (void *) pClient;508 return (void *) pClient;
555}509}
556510
@@ -568,24 +522,21 @@ void epicsShareAPI logClientShow (logClientId id, unsigned level)
568 printf ("log client: disconnected from log server at \"%s\"\n", pClient->name);522 printf ("log client: disconnected from log server at \"%s\"\n", pClient->name);
569 }523 }
570524
571 if (level>1) {
572 printf ("log client: sock=%s, connect cycles = %u\n",
573 pClient->sock==INVALID_SOCKET?"INVALID":"OK",
574 pClient->connectCount);
575 }
576
577 if (logClientPrefix) {525 if (logClientPrefix) {
578 printf ("log client: prefix is \"%s\"\n", logClientPrefix);526 printf ("log client: prefix is \"%s\"\n", logClientPrefix);
579 }527 }
580}
581528
582/*529 if (level>0) {
583 * logClientSendMessage (); deprecated530 printf ("log client: sock %s, connect cycles = %u\n",
584 */531 pClient->sock==INVALID_SOCKET?"INVALID":"OK",
585void logClientSendMessage ( logClientId id, const char * message )532 pClient->connectCount);
586{533 }
587 if ( !iocLogDisable ) {534 if (level>1) {
588 logClientSend (id, message);535 printf ("log client: %u bytes in buffer\n", pClient->nextMsgIndex);
536 if (pClient->nextMsgIndex)
537 printf("-------------------------\n"
538 "%.*s-------------------------\n",
539 (int)(pClient->nextMsgIndex), pClient->msgBuf);
589 }540 }
590}541}
591542
diff --git a/modules/libcom/src/log/logClient.h b/modules/libcom/src/log/logClient.h
index 1797bbb..3b3f63a 100644
--- a/modules/libcom/src/log/logClient.h
+++ b/modules/libcom/src/log/logClient.h
@@ -38,7 +38,6 @@ epicsShareFunc void epicsShareAPI iocLogPrefix(const char* prefix);
38/* deprecated interface; retained for backward compatibility */38/* deprecated interface; retained for backward compatibility */
39/* note: implementations are in iocLog.c, not logClient.c */39/* note: implementations are in iocLog.c, not logClient.c */
40epicsShareFunc logClientId epicsShareAPI logClientInit (void);40epicsShareFunc logClientId epicsShareAPI logClientInit (void);
41epicsShareFunc void logClientSendMessage (logClientId id, const char *message);
4241
43#ifdef __cplusplus42#ifdef __cplusplus
44}43}
diff --git a/modules/libcom/src/osi/Makefile b/modules/libcom/src/osi/Makefile
index ecbf4c2..71d8c3d 100644
--- a/modules/libcom/src/osi/Makefile
+++ b/modules/libcom/src/osi/Makefile
@@ -86,6 +86,7 @@ endif
8686
87Com_SRCS += osdSock.c87Com_SRCS += osdSock.c
88Com_SRCS += osdSockAddrReuse.cpp88Com_SRCS += osdSockAddrReuse.cpp
89Com_SRCS += osdSockUnsentCount.c
89Com_SRCS += osiSock.c90Com_SRCS += osiSock.c
90Com_SRCS += systemCallIntMech.cpp91Com_SRCS += systemCallIntMech.cpp
91Com_SRCS += epicsSocketConvertErrnoToString.cpp92Com_SRCS += epicsSocketConvertErrnoToString.cpp
diff --git a/modules/libcom/src/osi/os/Darwin/osdSockUnsentCount.c b/modules/libcom/src/osi/os/Darwin/osdSockUnsentCount.c
92new file mode 10064493new file mode 100644
index 0000000..20bd82b
--- /dev/null
+++ b/modules/libcom/src/osi/os/Darwin/osdSockUnsentCount.c
@@ -0,0 +1,19 @@
1/*************************************************************************\
2* EPICS BASE is distributed subject to a Software License Agreement found
3* in file LICENSE that is included with this distribution.
4\*************************************************************************/
5
6#define EPICS_PRIVATE_API
7#include "osiSock.h"
8
9/*
10 * epicsSocketUnsentCount ()
11 * See https://www.unix.com/man-page/osx/2/setsockopt
12 */
13int epicsSocketUnsentCount(SOCKET sock) {
14 int unsent;
15 socklen_t len = sizeof(unsent);
16 if (getsockopt(sock, SOL_SOCKET, SO_NWRITE, &unsent, &len) == 0)
17 return unsent;
18 return -1;
19}
diff --git a/modules/libcom/src/osi/os/Linux/osdSockUnsentCount.c b/modules/libcom/src/osi/os/Linux/osdSockUnsentCount.c
0new file mode 10064420new file mode 100644
index 0000000..3c0a8f9
--- /dev/null
+++ b/modules/libcom/src/osi/os/Linux/osdSockUnsentCount.c
@@ -0,0 +1,19 @@
1/*************************************************************************\
2* EPICS BASE is distributed subject to a Software License Agreement found
3* in file LICENSE that is included with this distribution.
4\*************************************************************************/
5
6#include <linux/sockios.h>
7#define EPICS_PRIVATE_API
8#include "osiSock.h"
9
10/*
11 * epicsSocketUnsentCount ()
12 * See https://linux.die.net/man/7/tcp
13 */
14int epicsSocketUnsentCount(SOCKET sock) {
15 int unsent;
16 if (ioctl(sock, SIOCOUTQ, &unsent) == 0)
17 return unsent;
18 return -1;
19}
diff --git a/modules/libcom/src/osi/os/WIN32/osdSockUnsentCount.c b/modules/libcom/src/osi/os/WIN32/osdSockUnsentCount.c
0new file mode 10064420new file mode 100644
index 0000000..fe68ead
--- /dev/null
+++ b/modules/libcom/src/osi/os/WIN32/osdSockUnsentCount.c
@@ -0,0 +1,26 @@
1/*************************************************************************\
2* EPICS BASE is distributed subject to a Software License Agreement found
3* in file LICENSE that is included with this distribution.
4\*************************************************************************/
5
6#define epicsExportSharedSymbols
7#define EPICS_PRIVATE_API
8#include "osiSock.h"
9#include <mstcpip.h>
10
11/*
12 * epicsSocketUnsentCount ()
13 * See https://docs.microsoft.com/en-us/windows/win32/api/mstcpip/ns-mstcpip-tcp_info_v0
14 */
15int epicsSocketUnsentCount(SOCKET sock) {
16#if defined (_WIN32) && WINVER >= _WIN32_WINNT_WIN10
17/* Windows 10 Version 1703 / Server 2016 */
18 DWORD infoVersion = 0, bytesReturned;
19 TCP_INFO_v0 tcpInfo;
20 int status;
21 if ((status = WSAIoctl(sock, SIO_TCP_INFO, &infoVersion, sizeof(infoVersion),
22 &tcpInfo, sizeof(tcpInfo), &bytesReturned, NULL, NULL)) == 0)
23 return tcpInfo.BytesInFlight;
24#endif
25 return -1;
26}
diff --git a/modules/libcom/src/osi/os/default/osdSockUnsentCount.c b/modules/libcom/src/osi/os/default/osdSockUnsentCount.c
0new file mode 10064427new file mode 100644
index 0000000..ef01e9b
--- /dev/null
+++ b/modules/libcom/src/osi/os/default/osdSockUnsentCount.c
@@ -0,0 +1,15 @@
1/*************************************************************************\
2* EPICS BASE is distributed subject to a Software License Agreement found
3* in file LICENSE that is included with this distribution.
4\*************************************************************************/
5
6#define EPICS_PRIVATE_API
7#include "osiSock.h"
8
9/*
10 * epicsSocketUnsentCount ()
11 */
12int epicsSocketUnsentCount(SOCKET sock) {
13 /* not implemented */
14 return -1;
15}
diff --git a/modules/libcom/src/osi/os/iOS/osdSockUnsentCount.c b/modules/libcom/src/osi/os/iOS/osdSockUnsentCount.c
0new file mode 10064416new file mode 100644
index 0000000..20bd82b
--- /dev/null
+++ b/modules/libcom/src/osi/os/iOS/osdSockUnsentCount.c
@@ -0,0 +1,19 @@
1/*************************************************************************\
2* EPICS BASE is distributed subject to a Software License Agreement found
3* in file LICENSE that is included with this distribution.
4\*************************************************************************/
5
6#define EPICS_PRIVATE_API
7#include "osiSock.h"
8
9/*
10 * epicsSocketUnsentCount ()
11 * See https://www.unix.com/man-page/osx/2/setsockopt
12 */
13int epicsSocketUnsentCount(SOCKET sock) {
14 int unsent;
15 socklen_t len = sizeof(unsent);
16 if (getsockopt(sock, SOL_SOCKET, SO_NWRITE, &unsent, &len) == 0)
17 return unsent;
18 return -1;
19}
diff --git a/modules/libcom/src/osi/osiSock.h b/modules/libcom/src/osi/osiSock.h
index 061619e..6e3b053 100644
--- a/modules/libcom/src/osi/osiSock.h
+++ b/modules/libcom/src/osi/osiSock.h
@@ -52,6 +52,14 @@ enum epicsSocketSystemCallInterruptMechanismQueryInfo {
52epicsShareFunc enum epicsSocketSystemCallInterruptMechanismQueryInfo 52epicsShareFunc enum epicsSocketSystemCallInterruptMechanismQueryInfo
53 epicsSocketSystemCallInterruptMechanismQuery ();53 epicsSocketSystemCallInterruptMechanismQuery ();
5454
55#ifdef EPICS_PRIVATE_API
56/*
57 * Some systems (e.g Linux and Windows 10) allow to check the amount
58 * of unsent data in the output queue.
59 * Returns -1 if the information is not available.
60 */
61epicsShareFunc int epicsSocketUnsentCount(SOCKET sock);
62#endif
5563
56/*64/*
57 * convert socket address to ASCII in this order65 * convert socket address to ASCII in this order

Subscribers

People subscribed via source and target branches