stephane / libmodbus

A Modbus library for Linux, Mac OS, FreeBSD and Windows
http://libmodbus.org
GNU Lesser General Public License v2.1
3.42k stars 1.74k forks source link

Unable to gracefully shutdown server thread. #173

Closed BrandonLWhite closed 10 years ago

BrandonLWhite commented 10 years ago

I need the ability to gracefully close/exit/abort a Modbus server thread that is blocked in modbus_receive(). At present, I am unable to get the blocking call to modbus_receive() to return. Invoking modbus_close() on the context from another thread does not abort the blocking call. On Linux, it appears to have no effect. On Windows, the modbus_close() call blocks indefinitely as well.

There does not seem to be any test coverage or examples of this, so my assumption is that this is not currently achievable in libmodbus. Please let me know if I am wrong, and I'd appreciate some guidance.

Note: Terminating the server thread is not a viable solution. I need to be able to gracefully get the call to modbus_receive to return.

stephane commented 10 years ago

It's better to use mailing list for question, isn't it?

So no, you can't gracefully interrupt modbus_receive but this change https://github.com/stephane/libmodbus/issues/95 could improve the situation.

Could you pursue talk on mailing list, please?

BrandonLWhite commented 10 years ago

Thanks for the response Stephane. I believe you are better off capturing this in a GitHub issue as it does revolve around a fairly substantial deficiency in using libmodbus to create a slave server.

I'd very much like to avoid using timeouts to kluge a solution.

My apologies that the original issue request came across like a forum post. My intent was to formally request that libmodbus gracefully stop a blocked server call to modbus_receive(). I would greatly appreciate seeing this issue reopened and addressed -- libmodbus would be even better and more robust as result.

stephane commented 10 years ago

The libmodbus goal is to implement Modbus protocol not to provide a slave or server out the box. In a safe network, you can stop your slave on specific Modbus request or implement signal handling, see my examples in mbtools (https://github.com/webstack/mbtools/blob/master/src/collect.c#L30)

vgrin commented 10 years ago

Hi, I have the same problem and the solution is two new fuction which shutdowns the server and reports to an application about the shutdown state. You can download the package from here;

http://vgrin.host56.com/software/libmodbus-master-withshutdown.zip

Additionally it contains test projects: tests\break-test-client.c and ests\break-test-server.c and projects files in tests\Win32\ . The break-test-server requires the code for UNIX – I have only Windows implementation, sorry :-). The server terminates itself from its own thread which waits some time. Of course the solution a bit risky because the library was not designed for multi-threading, but looks like not too much. :-) Maybe Stephane adds this to the official library.

pboettch commented 7 years ago

I tried your implementation of this signal-handlers in mbtools. It is calling modbus_close() which should make select() to return gracefully. It doesn't in my case (RTU). It seems the UNIX-way to do would be the self-pipe-trick.

Without change libmodbus, what I will do is to call select on my own before calling modbus_receive. And in my FD_SET I will add a self-pipe, which will be used by the main thread to signal the stop-condition to the slave-thread.

OmriSoudry commented 6 years ago

@pboettch can you please show the code you used in order to bypass this issue?

pboettch commented 6 years ago

I haz teh codez! ;-)

My tcp-server is a single thread server handling all clients sequentially if there are multiple requests at the same time. It's using poll() and is written in C++, here is snippet, I'll try to contribute a complete example along with my callback-implementation (which is used in this example and not yet integrated in master but pushed on my fork).

At global (or better object-scope) you will need a pipe for self-piping

int selfPipe_[2];

This is the "server"-code

    modbus_t *ctx = modbus_new_tcp("0.0.0.0", port_);
    int server_socket = modbus_tcp_listen(ctx, 2);
    if (server_socket < 0) {
      // error handling
      modbus_free(ctx);
      return;
    }

    const modbus_reply_callbacks_t callbacks{
        nullptr,
        &ModbusTCPServer::verify,
        &ModbusTCPServer::read,
        &ModbusTCPServer::write};
    modbus_set_reply_callbacks(ctx, &callbacks, this); // this is the code not yet upstream

    std::vector<struct pollfd> sockets; // using a vector as behind it is a C-array and can be passed to poll()

    sockets.push_back({selfPipe_[0], POLLIN, 0});  // add self-pipe to poll-list
    sockets.push_back({server_socket, POLLIN, 0}); // add the server-socket

    bool running = true;
    while (running) {

      int ret = poll(sockets.data(), sockets.size(), -1); // poll on all sockets, infinitely
      if (ret < 0) {
        // error handling
        break;
      }

      // poll woke up, something happened on one of the sockets

      struct pollfd new_socket { // for later use if new client connection
        - 1
      };

      for (auto p = sockets.begin(); p != sockets.end();) { // which socket has triggered the wakeup?
        if (p->fd == selfPipe_[0]) { // was it the self-pipe, we will stop here
          if (p->revents != 0) {
            running = false;
            break;
          }

        } else if (p->fd == server_socket) { // server-socket - new client
          if (p->revents & POLLIN) {
            std::cerr << "new connection\n";
            int fd = modbus_tcp_accept(ctx, &server_socket);
            if (fd >= 0)
              new_socket = {fd, POLLIN, 0}; // prepare poll-structure for new client
            else {
              // error handling
            }
          } else if (p->revents != 0) { // server socket closed, end
            running = false;
            break;
          }

        } else { // handle one client request
          if (p->revents & POLLIN) {
            modbus_set_socket(ctx, p->fd); // set the socket to libmodbus answers to the right client

            uint8_t query[MODBUS_RTU_MAX_ADU_LENGTH];
            int rc = modbus_receive(ctx, query);
            if (rc <= 0) { // connection has been closed actually by the client
              ::close(p->fd);
              p = sockets.erase(p); // remove it from the poll-array
              continue;
            }

            /* rc is the query size */
            rc = modbus_reply_callback(ctx, query, rc); // handle the reply (via the callbacks)
            if (rc < 0) {
              // error handling - invalid request
              ::close(p->fd); // close client
              p = sockets.erase(p);
              continue;
            }
          } else if (p->revents != 0) {
            // handle unexpected revents for user-connection
            ::close(p->fd);
            p = sockets.erase(p);
            continue;
          }
        }
        p++;
      }

      if (new_socket.fd != -1) // we have seen a new connection coming, doing it outside the loop because cannot add an element to vector while looping
        sockets.push_back(new_socket);
    }

    // stopping server -> closing all connections
    for (auto &p : sockets)
      ::close(p.fd);

    modbus_close(ctx);

    modbus_free(ctx);

You need to run a thread for the code above, before you need to create the self-pipe

if (pipe(selfPipe_) != 0) // oh oh!
    // error handling

and when you're done with your work to stop the thread you do

close(selfPipe_[1]);