BUG #18096: In edge-triggered epoll and kqueue, PQconsumeInput/PQisBusy are insufficient for correct async ops. - Mailing list pgsql-bugs

From PG Bug reporting form
Subject BUG #18096: In edge-triggered epoll and kqueue, PQconsumeInput/PQisBusy are insufficient for correct async ops.
Date
Msg-id 18096-2bd930f8f132fb4e@postgresql.org
Whole thread Raw
Responses Re: BUG #18096: In edge-triggered epoll and kqueue, PQconsumeInput/PQisBusy are insufficient for correct async ops.
List pgsql-bugs
The following bug has been logged on the website:

Bug reference:      18096
Logged by:          Masatoshi Fukunaga
Email address:      mah0x211@gmail.com
PostgreSQL version: 14.4
Operating system:   macOS 13.5.1, Ubuntu 22.04
Description:

When processing asynchronous commands, I call the `PQconsumeInput` and
`PQisBusy` functions to check if data has arrived, as shown below, but this
does not work correctly in edge trigger mode for epoll and kqueue.

In the edge trigger mode of epoll and kqueue, calls to the
`PQconsumeInput()` and `PQisBusy()` funct

I believe the following code is correct in the way it is instructed in the
manual.

> 34.4. Asynchronous Command Processing, the following is written.  
> https://www.postgresql.org/docs/current/libpq-async.html


```C
// on edge-trigger mode, this code does not work correctly
/**
 * check if the result is readable or not
 * @return 1: readable, 0: not readable, -1: error
 */
int is_readable(PGconn *conn) {
    if (!PQconsumeInput(conn)) {
        // caller should call PQerrorMessage to get error message
        return -1;
    } else if (!PQisBusy(conn)) {
        // caller can call PQgetResult to get the result
        return 1;
    }
    // caller should be wait for the socket to become readable
    return 0;
}
```

The `PQconsumeInput()` function reads input data by calling the
`pqReadData()` function internally and using the `pqsecure_read()` function
is used to read the input data.

However, the `pqReadData()` function will not call the `pqsecure_read()`
function until the `errno` is set to `EAGAIN` or `EWOULDBLOCK`, so if you
poll after the `PQisBusy()` call returns `1`, readable event will not fire
and will be permanently in a wait state.

By the way, I am aware that even if the result of a read by
`pqsecure_read()` does not result in `EAGAIN` or `EWOULDBLOCK`, the event
will still be raised if all the data in the socket has been read.

The problem seems to be that `PQisBusy()` is returning `1`, but the
preceding call to `PQconsumeInput()` has not read all the data in the
socket.

So, if I check the errno and branch the process as follows, it works fine.


```C
/**
 * check if the result is readable or not
 * @return 1: readable, 0: not readable, -1: error
 */
int is_readable(PGconn *conn) {
    int should_retry = 0;

RETRY:
    errno = 0;
    if (!PQconsumeInput(conn)) {
        // caller should call PQerrorMessage to get error message
        return -1;
    } 
    should_retry = errno != EAGAIN && errno != EWOULDBLOCK;

    if (!PQisBusy(conn)) {
        // caller can call PQgetResult to get the result
        return 1;
    } else if(should_retry) {
        // it is necessary to retry because the data has not been read
completely
        goto RETRY;
    }
    // caller should be wait for the socket to become readable
    return 0;
}
```


pgsql-bugs by date:

Previous
From: Thomas Munro
Date:
Subject: Re: FW: query pg_stat_ssl hang 100%cpu
Next
From: Michael Paquier
Date:
Subject: Re: FW: query pg_stat_ssl hang 100%cpu