Unclear behavior of monotonic_buffer_resource::release()
Arthur O'Dwyer

Created on 2018-06-10.00:00:00, last changed 2018-08-27.14:22:44.


Date: 2018-08-27.14:39:57

Proposed resolution:

This wording is relative to N4750.

  1. Modify [mem.res.monotonic.buffer.mem] as indicated:

    void release();

    -1- Effects: Calls upstream_rsrc->deallocate() as necessary to release all allocated memory. Resets *this to its initial state at construction.

Date: 2018-08-23.00:00:00

[ 2018-08-23 Batavia Issues processing ]

We liked Pablo's wording from the reflector discussion. Status to Open.

Date: 2018-06-23.00:00:00

[ 2018-06-23 after reflector discussion ]

Priority set to 2

Previous resolution [SUPERSEDED]:

This wording is relative to N4750.

[Drafting note: The resolution depicted below would make MSVC's and my-proposed-libc++'s implementations both conforming.]

  1. Modify [mem.res.monotonic.buffer.mem] as indicated:

    void release();

    -1- Effects: Calls upstream_rsrc->deallocate() as necessary to release all allocated memory. Resets the state of the initial buffer.

    -2- [Note: The memory is released back to upstream_rsrc even if some blocks that were allocated from this have not been deallocated from this. This function has an unspecified effect on next_buffer_size.end note]

Date: 2018-06-16.17:41:11

The effects of monotonic_buffer_resource::release() are defined as:

Calls upstream_rsrc->deallocate() as necessary to release all allocated memory.

This doesn't give any instruction on what to do with the memory controlled by the monotonic_buffer_resource which was not allocated, i.e., what to do with the initial buffer provided to its constructor.

Boost.Container's pmr implementation expels its initial buffer after a release(). Arthur O'Dwyer's proposed pmr implementation for libc++ reuses the initial buffer after a release(), on the assumption that this is what the average library user will be expecting.

#include <memory_resource>

int main() 
  char buffer[100];
    std::pmr::monotonic_buffer_resource mr(buffer, 100, std::pmr::null_memory_resource());
    mr.allocate(60);  // A
    std::pmr::monotonic_buffer_resource mr(buffer, 100, std::pmr::null_memory_resource());
    mr.allocate(60);  // B
    mr.allocate(60);  // C

Assume that allocation "B" always succeeds.
With the proposed libc++ implementation, allocations "A" and "C" both succeed.
With Boost.Container's implementation, allocations "A" and "C" both fail.
Using another plausible implementation strategy, allocation "A" could succeed but allocation "C" could fail. I have been informed that MSVC's implementation does this.

Which of these strategies should be permitted by the Standard?

Arthur considers "A and C both succeed" to be the obviously most user-friendly strategy, and really really hopes it's going to be permitted. Requiring "C" to succeed is unnecessary (and would render MSVC's current implementation non-conforming) but could help programmers concerned with portability between different implementations.

Another side-effect of release() which goes underspecified by the Standard is the effect of release() on next_buffer_size. As currently written, my interpretation is that release() is not permitted to decrease current_buffer_size; I'm not sure if this is a feature or a bug.

Consider this test case (taken from here):

std::pmr::monotonic_buffer_resource mr(std::pmr::new_delete_resource());
for (int i=0; i < 100; ++i) {
  mr.allocate(1);  // D

Arthur believes it is important that the 100th invocation of line "D" does not attempt to allocate 2100 bytes from the upstream resource.

Date User Action Args
2018-08-27 14:22:44adminsetmessages: + msg10136
2018-08-27 14:22:44adminsetstatus: new -> open
2018-06-25 00:47:25adminsetmessages: + msg9986
2018-06-16 16:33:06adminsetmessages: + msg9920
2018-06-10 00:00:00admincreate