diff --git a/junixsocket-native/pom.xml b/junixsocket-native/pom.xml index fa7e51ef1..8ee10e9d1 100644 --- a/junixsocket-native/pom.xml +++ b/junixsocket-native/pom.xml @@ -35,6 +35,52 @@ + + polling-eintr-test + + + polling-eintr-test + + + + + + com.github.maven-nar + nar-maven-plugin + + + ${junixsocket.native.default.toolPath} + + **/*.c + + + + ${junixsocket.native.default.toolPath} + + ${junixsocket.native.default.linkerName} + + + + polling-eintr-test + shared + + + + + + + default-nar-testCompile + + + true + + + + + + + + current-arch diff --git a/junixsocket-native/src/main/c/polling.c b/junixsocket-native/src/main/c/polling.c index e2f966d66..42eff8110 100644 --- a/junixsocket-native/src/main/c/polling.c +++ b/junixsocket-native/src/main/c/polling.c @@ -18,6 +18,7 @@ #include "config.h" #include "polling.h" +#include "polling_with_deadline.h" #include "exceptions.h" #include "filedescriptors.h" @@ -28,11 +29,6 @@ CK_IGNORE_CPP_WARNINGS_BEGIN #if __TOS_MVS__ # include # include -# include "gettod_zos.h" -static int clock_gettime(int mode, struct timespec* out) { - return gettimeofdayMonotonic(out); -} -# define CLOCK_MONOTONIC 1 #endif static jclass class_PollFd = NULL; @@ -87,10 +83,6 @@ static int eventToOp(int event) { #if defined(junixsocket_use_poll_for_accept) || defined(junixsocket_use_poll_for_read) -static uint64_t timespecToMillis(struct timespec* ts) { - return (uint64_t)ts->tv_sec * 1000 + (uint64_t)ts->tv_nsec / 1000000; -} - /* * Waits until the connection is ready to read/accept. * @@ -187,7 +179,7 @@ jint pollWithMillis(int handle, uint64_t millis) { struct timespec timeStart; struct timespec timeEnd; - if(clock_gettime(CLOCK_MONOTONIC, &timeEnd) == -1) { + if(pollMonotonicTime(&timeEnd) == -1) { return -1; } int ret; @@ -220,10 +212,10 @@ jint pollWithMillis(int handle, uint64_t millis) { } } int errnum = socket_errno; - if(clock_gettime(CLOCK_MONOTONIC, &timeEnd) == -1) { + if(pollMonotonicTime(&timeEnd) == -1) { return -1; } - int elapsed = (int)(timespecToMillis(&timeEnd) - timespecToMillis(&timeStart)); + int elapsed = (int)(pollTimespecToMillis(&timeEnd) - pollTimespecToMillis(&timeStart)); if(elapsed <= 0) { elapsed = 1; } @@ -418,7 +410,7 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_poll #if defined(_WIN32) ret = WSAPoll(pollFd, nfds, timeout); #else - ret = poll(pollFd, nfds, timeout); + ret = pollWithDeadline(pollFd, nfds, timeout); #endif if(ret == -1) { ret = 0; diff --git a/junixsocket-native/src/main/c/polling_with_deadline.h b/junixsocket-native/src/main/c/polling_with_deadline.h new file mode 100644 index 000000000..2030c4252 --- /dev/null +++ b/junixsocket-native/src/main/c/polling_with_deadline.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef polling_with_deadline_h +#define polling_with_deadline_h + +#include "config.h" + +#if __TOS_MVS__ +#include "gettod_zos.h" +#endif + +static int pollMonotonicTime(struct timespec* out) { +#if __TOS_MVS__ + return gettimeofdayMonotonic(out); +#else + return clock_gettime(CLOCK_MONOTONIC, out); +#endif +} + +static uint64_t pollTimespecToMillis(const struct timespec* ts) { + return (uint64_t)ts->tv_sec * 1000 + (uint64_t)ts->tv_nsec / 1000000; +} + +#if !defined(_WIN32) + +static int pollWithDeadline(struct pollfd* pollFd, nfds_t nfds, int timeout) { + uint64_t deadlineMillis = 0; + + if(timeout > 0) { + struct timespec now; + if(pollMonotonicTime(&now) == -1) { + return -1; + } + deadlineMillis = pollTimespecToMillis(&now) + (uint64_t)timeout; + } + + for(;;) { + int ret = poll(pollFd, nfds, timeout); + if(ret != -1 || socket_errno != EINTR) { + return ret; + } + + if(timeout > 0) { + struct timespec now; + if(pollMonotonicTime(&now) == -1) { + return -1; + } + + uint64_t nowMillis = pollTimespecToMillis(&now); + if(nowMillis >= deadlineMillis) { + return 0; + } + timeout = (int)(deadlineMillis - nowMillis); + } + } +} + +#endif /* !_WIN32 */ + +#endif /* polling_with_deadline_h */ diff --git a/junixsocket-native/src/test/c/polling-eintr-test.c b/junixsocket-native/src/test/c/polling-eintr-test.c new file mode 100644 index 000000000..83ad4463b --- /dev/null +++ b/junixsocket-native/src/test/c/polling-eintr-test.c @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#if defined(_WIN32) + +int main(void) { + return 0; +} + +#else + +#include +#include +#include +#include +#include + +#include "../../main/c/polling_with_deadline.h" + +static volatile sig_atomic_t signalReceived = 0; + +static void signalHandler(int signalNumber CK_UNUSED) { + signalReceived = 1; +} + +static void sleepMillis(long millis) { + struct timespec delay = { + .tv_sec = millis / 1000, + .tv_nsec = (millis % 1000) * 1000000, + }; + while(nanosleep(&delay, &delay) == -1 && errno == EINTR) { + } +} + +static int pipeWriteFd = -1; + +static void writeAfterDeadline(void) { + sleepMillis(2500); + char value = 1; + if(write(pipeWriteFd, &value, sizeof(value)) != sizeof(value)) { + _exit(EXIT_FAILURE); + } +} + +static int waitForChild(pid_t child) { + int status; + while(waitpid(child, &status, 0) == -1) { + if(errno != EINTR) { + return -1; + } + } + return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS ? 0 : -1; +} + +int main(void) { + int pipeFds[2]; + if(pipe(pipeFds) == -1) { + perror("pipe"); + return EXIT_FAILURE; + } + pipeWriteFd = pipeFds[1]; + + struct sigaction action = { + .sa_handler = signalHandler, + }; + sigemptyset(&action.sa_mask); + if(sigaction(SIGALRM, &action, NULL) == -1) { + perror("sigaction"); + return EXIT_FAILURE; + } + + pid_t writerChild = fork(); + if(writerChild == -1) { + perror("fork"); + return EXIT_FAILURE; + } else if(writerChild == 0) { + writeAfterDeadline(); + _exit(EXIT_SUCCESS); + } + + struct pollfd pollFd = { + .fd = pipeFds[0], + .events = POLLIN, + }; + alarm(1); + int ret = pollWithDeadline(&pollFd, 1, 2000); + alarm(0); + + int childFailed = waitForChild(writerChild); + close(pipeFds[0]); + close(pipeFds[1]); + + if(childFailed != 0) { + fprintf(stderr, "The writer child failed\n"); + return EXIT_FAILURE; + } + if(!signalReceived) { + fprintf(stderr, "SIGALRM was not received\n"); + return EXIT_FAILURE; + } + if(ret != 0) { + fprintf(stderr, "Expected timeout after EINTR, got poll result %d\n", ret); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +#endif