Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions junixsocket-native/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,52 @@
<junixsocket.install.skip></junixsocket.install.skip>
</properties>
<profiles>
<profile>
<id>polling-eintr-test</id>
<activation>
<property>
<name>polling-eintr-test</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.github.maven-nar</groupId>
<artifactId>nar-maven-plugin</artifactId>
<configuration>
<c>
<toolPath>${junixsocket.native.default.toolPath}</toolPath>
<testIncludes>
<testInclude>**/*.c</testInclude>
</testIncludes>
</c>
<linker>
<prefix>${junixsocket.native.default.toolPath}</prefix>
<toolPath></toolPath>
<name>${junixsocket.native.default.linkerName}</name>
</linker>
<tests>
<test>
<name>polling-eintr-test</name>
<link>shared</link>
</test>
</tests>
</configuration>
<executions>
<execution>
<!-- Native tests are executables, not JNI shared libraries. -->
<id>default-nar-testCompile</id>
<configuration>
<linker>
<clearDefaultOptions>true</clearDefaultOptions>
</linker>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>current-arch</id>
<activation>
Expand Down
18 changes: 5 additions & 13 deletions junixsocket-native/src/main/c/polling.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "config.h"
#include "polling.h"
#include "polling_with_deadline.h"

#include "exceptions.h"
#include "filedescriptors.h"
Expand All @@ -28,11 +29,6 @@ CK_IGNORE_CPP_WARNINGS_BEGIN
#if __TOS_MVS__
# include <sys/time.h>
# include <sys/select.h>
# 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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -418,7 +410,7 @@ JNIEXPORT jint JNICALL Java_org_newsclub_net_unix_NativeUnixSocket_poll
#if defined(_WIN32)
ret = WSAPoll(pollFd, nfds, timeout);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is Windows excluded from this logic?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change addresses POSIX poll() returning EINTR. WSAPoll does not appear to document an equivalent interruption error (and I did use some AI to search through and I couldn't find an equivalent either). If there is an equivalent Windows case that should have the same behavior, I’m open to handling it here as well.

#else
ret = poll(pollFd, nfds, timeout);
ret = pollWithDeadline(pollFd, nfds, timeout);
#endif
if(ret == -1) {
ret = 0;
Expand Down
76 changes: 76 additions & 0 deletions junixsocket-native/src/main/c/polling_with_deadline.h
Original file line number Diff line number Diff line change
@@ -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 */
123 changes: 123 additions & 0 deletions junixsocket-native/src/test/c/polling-eintr-test.c
Original file line number Diff line number Diff line change
@@ -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 <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#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