Search code examples
javasemaphorejna

Posting to a POSIX semaphore using JNA


I am trying to post to a semaphore using JNA on a Linux machine. For some reason, I always receive a 22 error (invalid argument) even for this simple example. In my understanding, should the below code not open a POSIX semaphore, post to it and close it again?

public class Sample {

  private static final int O_CREAT = 0x40;

  public static void main(String[] args) throws Exception {
    File notifier = new File("/tmp", "_test" + new Random().nextInt());
      if (!notifier.isFile() && !notifier.createNewFile()) {
        throw new IllegalStateException("Could not create notifier: " + notifier);
      }

      SempahoreLibrary library = Native.load("c", SempahoreLibrary.class);
      Pointer semaphore = library.sem_open(notifier.getAbsolutePath(), O_CREAT, 666, 0);
      try {
        library.sem_post(semaphore);
      } finally {
        library.sem_close(semaphore);
      }
  }

  interface SempahoreLibrary extends Library {
    Pointer sem_open(String name, int flags, int mode, int value) throws LastErrorException;
    int sem_post(Pointer pointer) throws LastErrorException;
    int sem_close(Pointer pointer) throws LastErrorException;
  }
}

Solution

  • I initially couldn't make it work with JNR either (strongly recommended over JNA), and got curious. Writing it in C helped.. :)

    An strace on the C port made it clear you don't have to create a file upfront and then "map" the semaphore to it. Also using the full path is wrong, because semaphores are created in /dev/shm and the "/" in the path screws up everything:

    futex(0x7f731b1190d0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
    openat(AT_FDCWD, "/dev/shm/sem.sema", O_RDWR|O_NOFOLLOW) = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=32, ...}) = 0
    

    So you should be able to remove the whole file/path creation and just use a regular non-path name for the semaphore in sem_open. Also the file mode should be octal, and you should make sure to also load the pthread library - it's required.

    Here is a working example in C:

    // clang -Wall sema.c -lpthread
    
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <semaphore.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    
    int main(int argc, char** argv)
    {
        sem_t* s = sem_open("notifier", O_CREAT, 0644, 0);
    
        if (!s) {
            perror("sem_open");
            exit(errno);
        }
    
        printf("s: %p\n", s);
    
        sem_post(s);
    
        int value = -1;
        sem_getvalue(s, &value);
        printf("value: %d\n", value);
    
        sem_wait(s);
        sem_getvalue(s, &value);
        printf("value: %d\n", value);
    
        sem_close(s);
    
        exit(EXIT_SUCCESS);
    }
    

    Here is a working Java version using JNR:

    import jnr.ffi.LastError;
    import jnr.ffi.LibraryLoader;
    import jnr.ffi.Pointer;
    import jnr.ffi.Runtime;
    
    public class Semaphore
    {
        private static final int O_CREAT = 0x40;
    
        public interface SempahoreLibrary
        {
            Pointer sem_open(String name, int flags, int mode, int value);
            int sem_post(Pointer pointer);
            int sem_close(Pointer pointer);
        }
    
        public static void main(String[] args) throws Exception
        {
    
            LibraryLoader<SempahoreLibrary> loader = LibraryLoader.create(SempahoreLibrary.class);
            loader.library("c");
            loader.library("pthread");
    
            SempahoreLibrary library = loader.load();
            jnr.ffi.Runtime runtime = Runtime.getRuntime(library);
    
            Pointer semaphore = library.sem_open("notifier", O_CREAT, 0644, 0);
            if (semaphore == null)
            {
                int errno = LastError.getLastError(runtime);
                System.out.println("sem_open: " + errno);
                System.exit(errno);
            }
    
            System.out.println("semaphore: " + Long.toHexString(semaphore.address()));
    
            try
            {
                int error = library.sem_post(semaphore);
                System.out.println("post: " + (error == 0 ? "OK" : LastError.getLastError(runtime)));
            }
            finally
            {
                int error = library.sem_close(semaphore);
                System.out.println("close: " + (error == 0 ? "OK" : LastError.getLastError(runtime)));
            }
        }
    }