Проблема JDK: может прерваться другой поток, заблокированный в операции ввода-вывода, с сигналом - PullRequest
0 голосов
/ 09 декабря 2018

Реализация OpenJDK (часть linux) много использует сигнал для прерывания другого потока, заблокированного в собственной операции ввода-вывода.Основная идея заключается в том, что отправка сигнала пробуждения целевому потоку приведет к возврату блокирующего вызова IO с EINTR.

Например, прерывание заблокированного потока при асинхронном закрытии используемого fd:

https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/solaris/native/java/net/linux_close.c

    static int closefd(int fd1, int fd2) {
        ...
        /*
         * Send a wakeup signal to all threads blocked on this
         * file descriptor.
         */
        threadEntry_t *curr = fdEntry->threads;
        while (curr != NULL) {
            curr->intr = 1;
            pthread_kill( curr->thr, sigWakeup );
            curr = curr->next;
        }
        ...
    }

другой пример - прерывание канала сокета:

https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/nio/ch/SocketChannelImpl.java

protected void implCloseSelectableChannel() throws IOException {
    ...
    // Signal native threads, if needed.  If a target thread is not
    // currently blocked in an I/O operation then no harm is done since
    // the signal handler doesn't actually do anything.
    //
    if (readerThread != 0)
        NativeThread.signal(readerThread);

    if (writerThread != 0)
        NativeThread.signal(writerThread);
    ...
}

Но может быть гонка, в которойзаблокированный поток пропустит сигнал и не будет прерван.

Чтобы продемонстрировать, я написал небольшой фрагмент тестового кода:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig) {
    printf("singal\n");
}

int main() {
    // set-up an interrupting signal handler
    signal(SIGINT, handler);

    // not blocking operation, prepare to block
    // if the wake-up signal arrived during this
    // operation. we'll miss it.
    for (int i = 0; i < 2100000000; ++i) {
        int j = i * i;
    }
    printf("not blocking operation done\n");

    // blocking io operation
    unsigned int remain = sleep(10);
    printf("blocking io operation done, remain %u\n", remain);

    return 0;
}

Если сигнал пробуждения поступил непосредственно перед тем, как заблокированный поток входит в ядро, чтобы ожидать ввода-вывода, например, выполняяКакой-то код-обертка glibc, заблокированный поток пропустит этот сигнал навсегда и не сможет быть прерван.

Вывод, когда сигнал поступил до ввода / вывода, выглядит следующим образом:

./a.out
^Csingal
^Csingal
^Csingal
^Csingal
^Csingal
not blocking operation done
blocking io operation done, remain 0

И вывод, когда сигналприбыл после блокировки IO:

./a.out
not blocking operation done
^Csingal
blocking io operation done, remain 6

Это ошибка JDK или я что-то пропустил?

1 Ответ

0 голосов
/ 09 декабря 2018

«Это ошибка JDK или я что-то упустил?»

вы упустили важный момент: ошибка - это несоответствие между спецификацией и реализацией.Спецификация Java ничего не говорит о сигналах Linux, что означает, что она может реагировать на них любым способом, и это нельзя квалифицировать как ошибку.

Чтобы доказать существование ошибки в JDK, вы должны найти утверждение в спецификации JVM , которое, по вашему мнению, нарушено, и создать тест, который демонстрирует, что JVM ведет себя не так, как предписаноэто утверждение.Поскольку все утверждения в спецификации JVM выражаются в виде байт-кода, тест должен быть написан на Java (или любом другом языке JVM) и скомпилирован в файл класса.

...