/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.store;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A guard that is created for every {@link ByteBufferIndexInput} that tries on best effort
 * to reject any access to the {@link ByteBuffer} behind, once it is unmapped. A single instance
 * of this is used for the original and all clones, so once the original is closed and unmapped
 * all clones also throw {@link AlreadyClosedException}, triggered by a {@link NullPointerException}.
 * <p>
 * This code uses the trick that is also used in
 * {@link java.lang.invoke.MutableCallSite#syncAll(java.lang.invoke.MutableCallSite[])} to
 * invalidate switch points. It also yields the current thread to give other threads a chance
 * to finish in-flight requests...
 */
final class ByteBufferGuard {
  
  /**
   * Pass in an implementation of this interface to cleanup ByteBuffers.
   * MMapDirectory implements this to allow unmapping of bytebuffers with private Java APIs.
   */
  @FunctionalInterface
  static interface BufferCleaner {
    void freeBuffer(String resourceDescription, ByteBuffer b) throws IOException;
  }
  
  private final String resourceDescription;
  private final BufferCleaner cleaner;
  
  /** not volatile, we use store-store barrier! */
  private boolean invalidated = false;
  
  /** the actual store-store barrier. */
  private final AtomicInteger barrier = new AtomicInteger();
  
  /**
   * Creates an instance to be used for a single {@link ByteBufferIndexInput} which
   * must be shared by all of its clones.
   */
  public ByteBufferGuard(String resourceDescription, BufferCleaner cleaner) {
    this.resourceDescription = resourceDescription;
    this.cleaner = cleaner;
  }
  
  /**
   * Invalidates this guard and unmaps (if supported).
   */
  public void invalidateAndUnmap(ByteBuffer... bufs) throws IOException {
    if (cleaner != null) {
      invalidated = true;
      // this should trigger a happens-before - so flushes all caches
      barrier.lazySet(0);
      Thread.yield();
      for (ByteBuffer b : bufs) {
        cleaner.freeBuffer(resourceDescription, b);
      }
    }
  }
  
  private void ensureValid() {
    if (invalidated) {
      // this triggers an AlreadyClosedException in ByteBufferIndexInput:
      throw new NullPointerException();
    }
  }
  
  public void getBytes(ByteBuffer receiver, byte[] dst, int offset, int length) {
    ensureValid();
    receiver.get(dst, offset, length);
  }
  
  public byte getByte(ByteBuffer receiver) {
    ensureValid();
    return receiver.get();
  }
  
  public short getShort(ByteBuffer receiver) {
    ensureValid();
    return receiver.getShort();
  }
  
  public int getInt(ByteBuffer receiver) {
    ensureValid();
    return receiver.getInt();
  }
  
  public long getLong(ByteBuffer receiver) {
    ensureValid();
    return receiver.getLong();
  }
  
  public byte getByte(ByteBuffer receiver, int pos) {
    ensureValid();
    return receiver.get(pos);
  }
  
  public short getShort(ByteBuffer receiver, int pos) {
    ensureValid();
    return receiver.getShort(pos);
  }
  
  public int getInt(ByteBuffer receiver, int pos) {
    ensureValid();
    return receiver.getInt(pos);
  }
  
  public long getLong(ByteBuffer receiver, int pos) {
    ensureValid();
    return receiver.getLong(pos);
  }
    
}

@@ -1351,162 +1351,75 @@
 ode 
-us
+tri
 es t
-he trick that is also used in%0A * %7B@link java.lang.invoke.MutableCallSite#syncAll(java.lang.invoke.MutableCallSite%5B%5D)%7D to%0A * invalidate switch points
+o hopefully flush any CPU caches using a store-store barrier
 . It
@@ -1434,16 +1434,19 @@
 elds the
+%0A *
  current
@@ -1483,19 +1483,16 @@
 a chance
-%0A *
  to fini
@@ -1970,17 +1970,17 @@
  %0A  /** 
-n
+N
 ot volat
@@ -1986,36 +1986,42 @@
 tile
-, we use store-store barrier
+; see comments on visibility below
 ! */
@@ -2073,18 +2073,17 @@
 /** 
-the actual
+Used as a
  sto
@@ -2098,17 +2098,37 @@
  barrier
-.
+; see comments below!
  */%0A  pr
@@ -2697,116 +2697,696 @@
  // 
-t
+T
 his 
+call 
 should 
-trigger a happens-before - so flushes all caches%0A      barrier.lazySet(0);%0A      Thread.yield();
+hopefully flush any CPU caches and as a result make%0A      // the %22invalidated%22 field update visible to other threads. We specifically%0A      // don't make %22invalidated%22 field volatile for performance reasons, hoping the%0A      // JVM won't optimize away reads of that field and hardware should ensure%0A      // caches are in sync after this call. This isn't entirely %22fool-proof%22 %0A      // (see LUCENE-7409 discussion), but it has been shown to work in practice%0A      // and we count on this behavior.%0A      barrier.lazySet(0);%0A      // we give other threads a bit of time to finish reads on their ByteBuffer...:%0A      Thread.yield();%0A      // finally unmap the ByteBuffers:
 %0A   

Fix comments as suggested by Dawid