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:

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, andmx1 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.