SchodMC
SchodMC

Reputation: 33

PostgreSQL connect asynchronously with epoll_wait

I whant to work with PostgreSQL (9.1) asynchronously in my linux project. For this I have to use epoll_wait (because of other parts of the application). The goal will be to finally use epoll in edge triggered mode. But I cannot make the connection process work, even in non edge triggered mode. And I don't know why. However, when user name and password is correct, it works. But it also have to work when the password is wrong, too. In that case, I get some error I don't understand. :-/ Here is the code I use (the connection is already initialized with an PQconnectStart() and a parameter list that works fine with PQconnectdb()):

void ConnectDB(PGconn * connection)
{
    int pq_fd = PQsocket(connection);
    int epoll_fd = epoll_create1(0);
    struct epoll_event event;
    struct epoll_event *eventList = (epoll_event *)calloc(64, sizeof(epoll_event));

    event.data.fd = pq_fd;
    event.events = EPOLLOUT | EPOLLERR;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pq_fd, &event);

    while (true) {
        PostgresPollingStatusType pt = PQconnectPoll(connection);
        switch (pt)
        {
        case PGRES_POLLING_OK:
            printf("*** connection established!\n");
            return;

        case PGRES_POLLING_FAILED:
            printf("*** connection failed: %s\n", PQerrorMessage(connection));
            return;

        case PGRES_POLLING_ACTIVE:
            printf("   --- poll result: PGRES_POLLING_ACTIVE\n");
            break;

        case PGRES_POLLING_READING:
            printf("   --- poll result: PGRES_POLLING_READING - Modifiing epoll to EPOLLIN\n");
            event.events = EPOLLIN | EPOLLERR;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, PQsocket(connection), &event) == -1) {
                printf("epoll_ctl() error: %u: %s\n", errno, strerror(errno));
                exit(1);
            }
            break;

        case PGRES_POLLING_WRITING:
            printf("   --- poll result: PGRES_POLLING_WRITING - Modifiing epoll to EPOLLOUT\n");
            event.events = EPOLLOUT | EPOLLERR;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, PQsocket(connection), &event) == -1) {
                printf("epoll_ctl() error: %u: %s\n", errno, strerror(errno));
                exit(1);
            }
            break;
        }

        int n = epoll_wait(epoll_fd, eventList, 64, -1);
        if (n == -1) {
            printf("epoll_wait() error: %u: %s\n", errno, strerror(errno));
            exit(1);
        }
    }
}

And here is the output I get:

--- poll result: PGRES_POLLING_WRITING - Modifiing epoll to EPOLLOUT
--- poll result: PGRES_POLLING_READING - Modifiing epoll to EPOLLIN
--- poll result: PGRES_POLLING_READING - Modifiing epoll to EPOLLIN
--- poll result: PGRES_POLLING_READING - Modifiing epoll to EPOLLIN
--- poll result: PGRES_POLLING_WRITING - Modifiing epoll to EPOLLOUT
--- poll result: PGRES_POLLING_READING - Modifiing epoll to EPOLLIN
--- poll result: PGRES_POLLING_READING - Modifiing epoll to EPOLLIN
--- poll result: PGRES_POLLING_WRITING - Modifiing epoll to EPOLLOUT
epoll_ctl() error: 2: No such file or directory

Has anyone an idea?

Upvotes: 3

Views: 954

Answers (1)

Ctx
Ctx

Reputation: 18410

The postgresql client library first tries an ssl-connection and if that fails, it retries without ssl. This is done regardless of the reason why the connection failed and it does so without notifying the caller at all. So even if the error is a wrong password, the client library will close the filedescriptor and reopen the socket connection for plaintext.

If a filedescriptor is closed, it is automatically removed from the epoll-set (which results in your "No such file or directory" error message). So you would have to manually re-add it:

if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, PQsocket(connection), &event) == -1) {
    if (errno == ENOENT) {
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, PQsocket(connection), &event);
    } else {
        printf("epoll_ctl() error: %u: %s\n", errno, strerror(errno));
        exit(1);
    }
}

Another option would be to enable or disable ssl permanently, add sslmode=require or sslmode=disable to your connect string for this. But if you plan to use PGreset() (or encounter any other situation where the socket is closed and reopened transparently to the caller), you will have the same problem then.

Admittedly, this behaviour of the postgresql-client-library is not very epoll()-friendly. In the old times, when using select() or poll(), this was a non-issue, because there was no state in kernel like with epoll() now.

Upvotes: 4

Related Questions