This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

### 3072. [networking.ts] `DynamicBuffer` object lifetimes underspecified

Section: 16.2.4 [networking.ts::buffer.reqmts.dynamicbuffer], 17.6 [networking.ts::buffer.async.read], 17.8 [networking.ts::buffer.async.write], 17.10 [networking.ts::buffer.async.read.until] Status: New Submitter: Christopher Kohlhoff Opened: 2018-02-26 Last modified: 2020-09-06

Priority: 3

View other active issues in [networking.ts::buffer.reqmts.dynamicbuffer].

View all other issues in [networking.ts::buffer.reqmts.dynamicbuffer].

View all issues with New status.

Discussion:

Addresses: networking.ts

The `DynamicBuffer` overloads of `async_read` and `async_write`, and `async_read_until`, are underspecified with respect to the lifetime of the dynamic buffer argument `b`.

Asio's implementation (and the intended specification) performs `DECAY_COPY(b)` in the `async_read`, `async_write`, and `async_read_until` initiating functions. All operations performed on `b` are actually performed on that decay-copy, or on a move-constructed descendant of it. The copy is intended to refer to the same underlying storage and be otherwise interchangeable with the original in every way.

Most initiating functions' argument lifetimes are covered by [async.reqmts.async.lifetime]. As an rvalue reference it falls under the second bullet, which specifies that the object is copied (but doesn't say decay-copied).

The proposed resolution adds a postcondition for `DynamicBuffer` move construction, and specifies that `DECAY_COPY(b)` be used for each of these functions. The following two alternative resolutions may also be considered:

- Add an extra bullet to [async.reqmts.async.lifetime] to cover rvalue parameters (but specifically exclude CompletionTokens).

- Change the `DynamicBuffer` arguments to be by-value. (And also change the corresponding synchronous operations to be consistent.)

However, the proposed resolution below is presented as a change that minimizes the scope of the impact.

[2018-06-18 after reflector discussion]

Priority set to 3

Proposed resolution:

This wording is relative to N4711.

1. Edit 16.2.4 [networking.ts::buffer.reqmts.dynamicbuffer] as indicated:

   > -3- In Table 14, `x` denotes a value of type `X`, `x1` denotes a (possibly const) value of type `X`, and `mx1` denotes an xvalue of type `X`, `n` denotes a (possibly const) value of type `size_t`, and `u` denotes an identifier.

   > Table 14 — DynamicBuffer requirements
   > expression | type | assertion/note pre/post-conditions
   > --- | --- | ---
   > `X u(mx1);` |  | post: `u.size()` is equal to the prior value of `mx1.size()`. `u.max_size()` is equal to the prior value of `mx1.max_size()`. `u.capacity()` is equal to the prior value of `mx1.capacity()`. `u.data()` satisfies the ConstBufferSequence requirements (16.2.2 [buffer.reqmts.constbuffersequence]) as if copy constructed from the prior value of `mx1.data()`. All valid const or mutable buffer sequences that were previously obtained using `mx1.data()` or `mx1.prepare()` remain valid.

2. Edit 17.6 [networking.ts::buffer.async.read] as indicated:

   > -11- Let `bd` be the result of `DECAY_COPY(b)`. Data is placed into the dynamic buffer (16.2.4 [buffer.reqmts.dynamicbuffer]) object `bbd`. A mutable buffer sequence (16.2.1 [buffer.reqmts.mutablebuffersequence]) is obtained prior to each `read_some` call using `bd.prepare(N)`, where `N` is an unspecified value less than or equal to `bd.max_size() - bd.size()`. [Note: Implementations are encouraged to use `bd.capacity()` when determining `N`, to minimize the number of `read_some` calls performed on the stream. -- end note] After each `read_some` call, the implementation performs `bd.commit(n)`, where `n` is the return value from `read_some`.

   > […]

   > -13- The synchronous read operation continues until:

   > - `bd.size() == bd.max_size()`; or

   > - the completion condition returns `0`.

3. Edit 17.8 [networking.ts::buffer.async.write] as indicated:

   > -11- Let `bd` be the result of `DECAY_COPY(b)`. Data is written from the dynamic buffer (16.2.4 [buffer.reqmts.dynamicbuffer]) object `bd`. A constant buffer sequence (16.2.2 [buffer.reqmts.constbuffersequence]) is obtained using `bd.data()`. After the data has been written to the stream, the implementation performs `bd.consume(n)`, where `n` is the number of bytes successfully written.

   > […]

   > -13- The asynchronous write operation continues until:

   > - `bd.size() == 0`; or

   > - the completion condition returns `0`.

4. Edit 17.10 [networking.ts::buffer.async.read.until] as indicated:

   > -3- Effects: Let `bd` be the result of `DECAY_COPY(b)`. Initiates an asynchronous operation to read data from the buffer-oriented asynchronous read stream (17.1.2 [buffer.stream.reqmts.asyncreadstream]) object `stream` by performing zero or more asynchronous read_some operations on the stream, until the readable bytes of the dynamic buffer (16.2.4 [buffer.reqmts.dynamicbuffer]) object `bd` contain the specified delimiter `delim`.

   > -4- Data is placed into the dynamic buffer object `bd`. A mutable buffer sequence (16.2.1 [buffer.reqmts.mutablebuffersequence]) is obtained prior to each `async_read_some` call using `bd.prepare(N)`, where `N` is an unspecified value such that `N <= max_size() - size()`. [Note: Implementations are encouraged to use `bd.capacity()` when determining `N`, to minimize the number of asynchronous read_some operations performed on the stream. — end note] After the completion of each asynchronous `read_some` operation, the implementation performs `bd.commit(n)`, where `n` is the value passed to the asynchronous `read_some` operation's completion handler.

   > -5- The asynchronous `read_until` operation continues until:

   > - the readable bytes of `bd` contain the delimiter `delim`; or

   > - `bd.size() == bd.max_size()`; or

   > - an asynchronous `read_some` operation fails.

   > […]

   > -8- On completion of the asynchronous operation, if the readable bytes of `bd` contain the delimiter, `ec` is set such that `!ec` is `true`. Otherwise, if `bd.size() == bd.max_size()`, `ec` is set such that `ec == stream_errc::not_found`. If `bd.size() < bd.max_size()`, `ec` is the `error_code` from the most recent asynchronous `read_some` operation. `n` is the number of readable bytes in `bd` up to and including the delimiter, if present, otherwise `0`.