RFR : 8221696: MappedByteBuffer.force method to specify range

Andrew Dinn adinn at redhat.com
Tue Apr 23 16:15:01 UTC 2019

Hi Alan,

Thanks for looking at this.

On 22/04/2019 16:52, Alan Bateman wrote:
> The calculation to compute the page aligned address and then the
> distance from there to the end of the region is tricky to get right. I
> think you have it right, I'm just wondering if we could leverage the
> existing mappingOffset/mappingAddress methods so that we have only one
> place to audit. For example, suppose mappingOffset is changed to take an
> index. I think this would reduce the new force method down to:
> long offset = mappingOffset(index);
> force0(fd, mappingAddress(offset), offset + length);

I like this idea but I'd like to make sure I am clear about exactly how
it ought to work. If you will indulge me I will work through this by
describing first the status quo (as I perceive it) and then what my
change attempts before trying to reconcile the two.


There are 4 key locations of interest in the mapped byte range

[... map_base ... address ... address+limit ... address+capacity ...]
     |                     |
     +----- page size -----+

  - addresses to the left are numerically (i.e. as longs) lower than
    those to the right
  - map_base is aligned to page size -- it is obtained by rounding down
    address to a page boundary
  - consequently (address - map_base) is less than page size

n.b. the mmapped region may or may not commence at map_base but it will
always include map_base. Also, it will include bytes at all subsequent
addresses up to (address + capacity). Whether it includes or extends
beyond (address + capacity) is not really important.

mappingOffset() computes (address - map_base):

  int ps = Bits.pageSize();
  long offset = address % ps;
  return (offset >= 0) ? offset : (ps + offset);

The jiggery-pokery with ? : works round the rather unhelpful behaviour
of mod wrt negative longs.

I think it could, equivalently, compute it directly as:

  int ps = Bits.pageSize();
  long map_base = (address & ~(ps - 1));
  return address - map_base;

mappingAddress(mappingOffset) converts a previously retrieved
mappingOffset back to the map base:

   return (address - mappingOffset)
     == (address - (address - map_base))
     == map_base

mappingLength(mappingOffset) computes ((address + capacity) - map_base)
i.e. the byte count from start of page map_base to the last
(potentially) /writeable/ buffer byte.

  return capacity() + mappingOffset
    == capacity + (address - map_base)
    == (address + capacity) - map_base

arguably this method could just compute ((address + length) - map_base)
i.e. the byte count from map_base to the last /written/ byte -- but that
makes no real difference when performing a file-based flush.

The call to force0 is made as

  force0(fd, mappingAddress(offset), mappingLength(offset))

which is equivalent to

  force0(fd, map_base, capacity + (address - map_base))

My updated code:

When looking at an indexed location there are now 7 key locations of
interest in the mapped byte range

[... m_base ... a ... i_base ... a+i ... a+i+ln ... a+lim, a+cap ...]
     |             |  |             |
     +- page size -+  +- page size -+

  - for brevity, map_base is abbreviated to m_base, address to a,
    index_base to i_base, index to i, length to ln, limit to lim
    and capacity to cap
  - index is identifies the start of the region to be flushed
  - length is the length of the region to be flushed
  - (address + index) is the address of the byte at offset index
  - index_base is the largest page aligned address below (address + idx)
    i.e. it is obtained by rounding down the first byte of the flush
    region to a page boundary

index_base is computed as follows

  int ps = Bits.pageSize();
  long index_base = ((address + index) & ~(ps - 1));

My code is calling

  force0(fd, index_base, length + ((address + index) - i_base))

i.e. it the flushed range starts at the nearest page boundary below or
equal to (address + index) and includes the original length bytes plus
the intervening bytes between that page boundary and address.

When this is considered by analogy with the previous call then three
things stand out:

  1) in place of map_base for the first argument we have index_base
  2) in place of the extra included intervening bytes between map_base
     and address we have the extra included intervening bytes between
     index_base and address + index
  3) in place of capacity (default) demarcated bytes to be flushed we
     have length user demarcated bytes to be flushed

So, to retain those parallels detailed in 1, 2 and 3 we need
mappingOffset and mappingAddress to be overloaded and the originals
rerouted as follows

  int ps = Bits.pageSize();
  long index_address = address + index;
  long index_base = (index_address & ~(ps - 1));
  return index_address - index_base;

mappingOffset() => mappingOffset(0)

mappingAddress(mappingOffset, index)
  long index_address = address + index;
  return index_address - mappingOffset
    == (index_address - (index_address - index_base))
    == index_base

mappingAddress(mappingOffset) => mappingAddress(mappingOffset, 0)

mappingLength(mappingOffset, length)
  return length + mappingOffset

mappingLength(mappingOffset) =>
          mappingLength(mappingOffset, (long)capacity())

Does that look correct? If so I will prepare a new webrev accordingly.

> I don't expect any issues on Windows but I will test it to make sure.
Thank you. That would be very helpful.


Andrew Dinn
Senior Principal Software Engineer
Red Hat UK Ltd
Registered in England and Wales under Company Registration No. 03798903
Directors: Michael Cunningham, Michael ("Mike") O'Neill, Eric Shander

More information about the core-libs-dev mailing list