Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is the function call to "modbus_tcp_accept()" blocking ? #452

Closed
sritam2 opened this issue Oct 5, 2018 · 10 comments
Closed

Is the function call to "modbus_tcp_accept()" blocking ? #452

sritam2 opened this issue Oct 5, 2018 · 10 comments
Assignees

Comments

@sritam2
Copy link

sritam2 commented Oct 5, 2018

Good evening. I am using libmodbus in one of our projects. It is a very efficient library.
I have one doubt regarding one of the functions named - "modbus_tcp_accept()".

First I create the modbus TCP context using "modbus_new_tcp()". Then I allocate memory map using "modbus_mapping_new()". Next I create a socket using "modbus_tcp_listen()". I capture the return type of this function in a socket variable which I will pass it to my next call to modbus_tcp_accept(). The program flow is very simple as shown below:

  1. ctx = modbus_new_tcp(IP_ADDRESS, PORT_NO);
  2. mb_mapping = modbus_mapping_new(DISCRETE_INPUT_COILS_SIZE,
    DISCRETE_OUTPUT_COILS_SIZE,
    ANALOG_HOLDING_REGISTER_SIZE,
    ANALOG_INPUT_REGISTER_SIZE);
  3. modbus_socket = modbus_tcp_listen(ctx, 2);
  4. modbus_tcp_accept(ctx, &modbus_socket);
    DOUBT:
    Is the last call in step 4 to function "modbus_tcp_accept()" a blocking call?
    I tested in my demo application. All the above calls from step 1 till 3 are non-blocking. Please tell me if the last call in step 4 to "modbus_tcp_accept()" is blocking or not.

According to my experiment I think that it is a blocking call.

Please help me out with your answer.
Looking forward to help.

Thanks and Regards,
Sritam Paltasingh.

@timmi-on-rails
Copy link

The call to modbus_tcp_accept can be blocking, if there is no pending connection.

See modbus-tcp.c (libmodbus version 3.1.4):

int modbus_tcp_accept(modbus_t *ctx, int *s)
{

...

#ifdef HAVE_ACCEPT4
    /* Inherit socket flags and use accept4 call */
    ctx->s = accept4(*s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC);
#else
    ctx->s = accept(*s, (struct sockaddr *)&addr, &addrlen);
#endif

...

}

The documentation for accept on Windows (https://msdn.microsoft.com/de-de/library/windows/desktop/ms737526(v=vs.85).aspx) and Linux (https://linux.die.net/man/2/accept) both confirm my answer.

@sritam2
Copy link
Author

sritam2 commented Oct 7, 2018

Thank you so much. I agree to your answer. In my case there is no pending connection initially as no client is initiating a connection at the start. But server is first trying to initialize all its components and 1 of the components is modbus-tcp. But the call to "modbus_tcp_accept()"is becoming blocking. Is there any alternative way to make the call non-blocking or do I need to go for a multi-threading approach. Is there any signal or event notification mechanism.
Looking forward to your valuable advise.

Thanks and Regards,
Sritam Paltasingh.

@sritam2
Copy link
Author

sritam2 commented Oct 9, 2018

After playing around with my demo application the conclusion I draw is as follows:

  1. Call to "modbus_tcp_accept(ctx, modbus_socket)" is blocking if there are no pending connections from the client. This is the default behaviour.
  2. But if the default behaviour needs to be overridden, i.e., if you want "modbus_tcp_accept(ctx, modbus_socket)" to be non-blocking then make the "modbus_socket" non-blocking by setting the O_NONBLOCK flag. But in this case the call to "modbus_tcp_accept(ctx, modbus_socket)" will return immediately if there is no pending connection with an error code and "errno" will be set accirdingly.
  3. A mediocre solution to make the call somewhat non-blocking is to use "select()" or "poll()" with some timeout.
  4. The best solution to make it non-blocking as well as asynchronous is to associate the "modbus_socket" with SIGIO signal and register a signal handler for SIGIO. The sequence of steps for this is:
    * soc = modbus_tcp_listen(ctx, 2);
    * sig_set.sa_handler = termination_handler; // termination_handler is the user defined signal handler
    * sigemptyset(&sig_set.sa_mask);
    * sigaction(SIGIO, &sig_set, 0);
    * fcntl(soc, F_SETOWN, getpid());
    * int flags = fcntl(soc, F_GETFL);
    * fcntl(soc, F_SETFL, flags | O_ASYNC);
    In this way when ever a client connection is requested, signal handler(termination_handler in this example) function is called. "modbus_tcp_accept()" should now be called inside the signal handler(termination_handler in this example). The rest is upto the user.

Thanks and Regards,
Sritam Paltasingh.

@sritam2 sritam2 closed this as completed Oct 9, 2018
@timmi-on-rails
Copy link

Good ideas. I wanted to tell you about the non-blocking socket option aswell.
If modbus tcp would be the only thing your server needs to handle then I would go with the blocking call.
Constantly polling with non-blocking modbus_tcp_accept is probably burning the CPU.
Can you use select or does your server need to perform other actions while waiting for events?

About the signal handler. This solution sounds good, but maybe has a drawback aswell. I assume the signal handler is called on the main thread at any arbitrary moment, right? I think this can introduce issues just like multithreading. (Maybe I am wrong.) I think one needs some event loop and the signal handler should dispatch an event into the loop, whenever a connection is requested. This way you don't have to care about creating mutexes for shared memory.
Does that make sense? :-)

@pitti98
Copy link

pitti98 commented May 7, 2019

One interesting thing to note about nonblocking with modbus_tcp_accept is that a nonblocking listen socket does not seem to have been on the developer's mind when it was written.
modbus_tcp_accept receives the listening socket as an in-out parameter. If the accept fails, the socket is closed and the socket variable is set to 0.
Now, this has two problems:

  • if the socket is nonblocking and there are no connections to accept, EAGAIN is returned, and the socket is closed. This is not what you want.
  • The socket variable is set to zero, which is still a valid file descriptor value, namely that of stdin. If you don't check for errors, and just call modbus_tcp_accept again, an accept(0, ...) is tried, gets an ENOTSOCK error, and it closes your stdin. The socket variable should be set to -1 instead.

We had a pretty exciting debugging session because of this. Someone closed stdin repeatedly, and we did not find the cuplrit for a while. Especially because by default strace only traces the main thread if you don't give it the -f switch. So there was no close() in the trace but the descriptor magically became invalid - repeatedly.

@KsaweryRettinger
Copy link

I'm currently running a TCP/IP server on one the threads of my application. When trying to close the application (and with no incoming TCP/IP connection), i'm unable to terminate the thread correctly, since the modbus_tcp_pi_accept function blocks it. Is there a way to force the function to return?

Many thanks,
Ksawery

@stephane stephane self-assigned this Oct 24, 2019
@stephane stephane pinned this issue Oct 24, 2019
@stephane stephane unpinned this issue Oct 24, 2019
@timmi-on-rails
Copy link

@KsaweryRettinger Maybe you can close the socket and the function returns with an error? Have you tried?

@pboettch
Copy link
Contributor

Several possibilities exists to cleanly exit an accept which blocks. One is the self-pipe-trick, This issue shows some C-code (inside C++ containers are used)

#173

@KsaweryRettinger
Copy link

KsaweryRettinger commented Nov 12, 2019

Thank you for your replies. Simply closing the socket didn't help. In the end, I made the socket non-blocking, as follows:

ctx = modbus_new_tcp_pi(NULL, "1502");
modbus_set_indication_timeout(ctx, 1, 0);
socket = modbus_tcp_pi_listen(ctx, 1);
fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK);

I realize this might not be the most efficient use of my thread, but it works for now.

Regards,
Ksawery

@pboettch
Copy link
Contributor

This is more CPU intensive than using poll or select with a self-pipe. But maybe this is neglectable in your application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants