Futex32.java

/*
 * junixsocket
 *
 * Copyright 2009-2026 Christian Kohlschütter
 *
 * Licensed 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.newsclub.net.unix.memory;

import java.io.IOException;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.ValueLayout.OfInt;
import java.lang.invoke.VarHandle;
import java.util.concurrent.atomic.AtomicBoolean;

import org.newsclub.net.unix.MemoryImplUtilInternal;

final class Futex32 implements Futex {
  private static final VarHandle VH_INT = ValueLayout.OfInt.JAVA_INT.varHandle();

  private static final int MUTEX_UNLOCKED = 0;
  private static final int MUTEX_LOCKED = 1;
  private static final int MUTEX_LOCKED_WAITING = 2;

  private final MemorySegment ms;
  private final boolean zeroOnClose;
  private final AtomicBoolean closed = new AtomicBoolean(false);

  Futex32(MemorySegment ms, boolean zeroOnClose) throws IOException {
    if (!ms.isAccessibleBy(Thread.currentThread())) {
      throw new IllegalStateException("Cannot access this MemorySegment from the current thread");
    }
    if ((ms.address() & 3) != 0) {
      throw new IOException("Not aligned");
    }
    if (ms.byteSize() != SharedMemory.FUTEX32_SEGMENT_SIZE) {
      throw new IOException("MemorySegment must be exactly 4 bytes long");
    }
    this.ms = ms;
    this.zeroOnClose = zeroOnClose;

    // Make sure the 32-bit value is accessible (page-in memory)
    SharedMemory.UTIL.madvise(ms.address(), SharedMemory.FUTEX32_SEGMENT_SIZE,
        MemoryImplUtilInternal.MADV_WILLNEED, true);
  }

  @Override
  public void close() {
    if (!closed.getAndSet(true)) {
      if (zeroOnClose && ms != null) {
        ms.set(OfInt.JAVA_INT, 0, 0);
      }
    }
  }

  @Override
  public boolean tryWait(int ifValue, int timeoutMillis) throws IOException {
    return SharedMemory.UTIL.futexWait(ms.address(), ifValue, timeoutMillis);
  }

  @Override
  public boolean tryWake(boolean wakeAll) throws IOException {
    return SharedMemory.UTIL.futexWake(ms.address(), wakeAll);
  }

  @Override
  public boolean isClosed() {
    return closed.get();
  }

  MemorySegment getMemorySegment() {
    return ms;
  }

  @Override
  public boolean isInterProcess() {
    return SharedMemory.UTIL.futexIsInterProcess();
  }

  private final class Mutex32 implements SharedMutex {
    @Override
    public void close() throws IOException {
      Futex32.this.close();
    }

    @Override
    public boolean tryLock(int timeoutMillis) throws IOException {
      int w = (int) VH_INT.compareAndExchange(ms, 0, MUTEX_UNLOCKED, MUTEX_LOCKED);
      if (w == MUTEX_UNLOCKED) {
        return true;
      }

      if (w != MUTEX_LOCKED_WAITING) {
        w = (int) VH_INT.getAndSet(ms, 0, MUTEX_LOCKED_WAITING);
      }

      if (w == MUTEX_UNLOCKED) {
        return true;
      }

      long start = System.currentTimeMillis();
      while (!Thread.interrupted()) {
        if (!Futex32.this.tryWait(MUTEX_LOCKED_WAITING, timeoutMillis)) {
          if (isClosed()) {
            return false;
          }
        }
        w = (int) VH_INT.getAndSet(ms, 0, MUTEX_LOCKED_WAITING);
        if (w == MUTEX_UNLOCKED) {
          return true;
        }
        if (timeoutMillis != 0) {
          timeoutMillis -= (int) (System.currentTimeMillis() - start);
          if (timeoutMillis <= 0) {
            return false;
          }
        }
      }
      return false;
    }

    @Override
    public void unlock() throws IOException {
      int c = (int) VH_INT.getAndAdd(ms, 0, -1);
      switch (c) {
        case MUTEX_UNLOCKED:
        case MUTEX_LOCKED:
          break;
        default:
          VH_INT.set(ms, 0, MUTEX_UNLOCKED);
          Futex32.this.tryWake(false);
      }
    }

    @Override
    public boolean isReentrant() {
      return false;
    }

    @Override
    public boolean isInterProcess() {
      return Futex32.this.isInterProcess();
    }
  }

  SharedMutex mutex() {
    return new Mutex32();
  }
}