/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
/*
**********************************************************************
* Poller.c :
* JNI code for use with Poller.java, principally to take advantage
* of poll() or /dev/poll multiplexing.
*
* One will need Solaris 8 or Solaris 7 with adequate patches to take
* advantage of the /dev/poll performance enhancements, though any
* version of Solaris 7 will automatically use the kernel poll()
* caching. And poll() will function in 2.5.1 and 2.6 as well, but
* will not perform well for large numbers of file descriptors.
*
* Several assumptions have been made to simplify this code :
* 1> At most MAX_HANDLES (32) separate pollable entities are currently
* supported.
* 2> Global synchronization from Java is assumed for all init, create
* and destroy routines. Per Object (handle passed in) synchronization
* is required for all AddFd, RemoveFd, IsMember, and Wait routines.
* 3> It is currently up to the user to handle waking up an
* existing nativeWait() call to do an addfd or removefd on
* that set...could implement that here with an extra pipe, or
* with a pair of loopback sockets in Poller.java or user code.
* In most cases interruption is not necessary for deletions,
* so long as deletions are queued up outside the Poller class
* and then executed the next time waitMultiple() returns.
* 4> /dev/poll performance could be slightly improved by coalescing
* adds/removes so that a write() is only done before the ioctl
* (DP_POLL), but this complicates exception handling and sees
* only modest performance gains so wasn't done.
* 5> /dev/poll does not report errors on attempts to remove non-
* extant fds, but a future bug fix to the /dev/poll device driver
* should solve this problem.
* 6> Could add simpler code for pre-Solaris 7 releases which will
* perform slightly better on those OSs. But again there
* are only modest gains to be had from these new code paths,
* so they've been omitted here.
*
* Compile "cc -G -o <dest_dir>/libpoller.so -I ${JAVA_HOME}/include " \
* -I ${JAVA_HOME}/include/solaris Poller.c" and place the <dest_dir>
* in your LD_LIBRARY_PATH
*
**********************************************************************
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <malloc.h>
#include <fcntl.h>
/*
* Remove "_NOT"s to turn on features
* Append "_NOT" to turn off features.
* Use of /dev/poll requires both the include file and kernel driver.
*/
#define DEBUG_NOT
#define DEVPOLL_NOT
#ifdef DEVPOLL
#include <sys/devpoll.h>
#endif
#include "Poller.h"
#define MAX_HANDLES 32
#ifdef DEBUG
#define DBGMSG(x) printf x
#define ASSERT(x) {if (!(x)) \
printf("assertion(%s) failed at line : %d\n",#x,__LINE__);}
#define CHECK_HANDLE(x) check_handle(x)
#else
#define DBGMSG(x)
#define ASSERT(x)
#define CHECK_HANDLE(x)
#endif
/*
* Globals ...protect all with a global synchronization object.
*/
static int Current_handle = 0;
static int Use_devpoll = 0;
static int Max_index = 0;
/*
* Per Poller object data.
* Must be synchronized on a per Poller object basis.
*/
typedef struct ioevent {
int inuse;
int devpollfd;
int last_index;
int total_free;
int left_events;
int max_index;
pollfd_t *pfd;
} ioevent_t;
static ioevent_t IOE_handles[MAX_HANDLES];
/*
* Exceptions to be thrown.
* Note : assuming all illegal argument and NULL pointer checks
* have already been done by the Java calling methods.
*/
static jint throwOutOfMemoryError(JNIEnv *env, const char * cause)
{
(*env)->ThrowNew(env, (*env)->FindClass(env,"java/lang/OutOfMemoryError"),
cause);
return -1;
}
static jint throwInterruptedIOException(JNIEnv *env, const char * cause)
{
(*env)->ThrowNew(env,
(*env)->FindClass(env,"java/io/InterruptedIOException"),
cause);
return -1;
}
static jint throwIllegalStateException(JNIEnv *env, const char * cause)
{
(*env)->ThrowNew(env,
(*env)->FindClass(env,"java/lang/IllegalStateException"),
cause);
return -1;
}
#define MEMORY_EXCEPTION(str) throwOutOfMemoryError(env, "Poller:" str)
#define STATE_EXCEPTION(str) throwIllegalStateException(env, "Poller:" str)
#define INTERRUPT_EXCEPTION(str) throwInterruptedIOException(env, \
"Poller:" str)
jint addfd(JNIEnv *, ioevent_t *, jint, jshort);
jint removefd(JNIEnv *, ioevent_t *, jint);
/*
* Class Poller
* Method: nativeInit
* Signature: ()I
*
* Only to be called once, right after this library is loaded,
* so no need to deal with reentrancy here.
* Could do as a pragma ini, but that isn't as portable.
*/
JNIEXPORT jint JNICALL Java_Poller_nativeInit(JNIEnv *env, jclass cls)
{
int testdevpollfd;
int i;
#ifdef DEVPOLL
/*
* See if we can use this much faster method
* Note : must have fix for BUGID # 4223353 or OS can crash!
*/
testdevpollfd = open("/dev/poll",O_RDWR);
if (testdevpollfd >= 0) {
/*
* If Solaris 7, we need a patch
* Until we know what string to search for, we'll play it
* safe and disable this for Solaris 7.
*/
if (!strcmp(name.release,"5.7"))
{
Use_devpoll = 0;
}
else
{
Use_devpoll = 1;
}
}
DBGMSG(("Use_devpoll=%d\n" ,Use_devpoll));
close(testdevpollfd);
#endif
/*
* For now, we optimize for Solaris 7 if /dev/poll isn't
* available, as it is only a small % hit for Solaris < 7.
* if ( (Use_devpoll == 0) && !strcmp(name.release,"5.6") )
* Use_sol7opt = 0;
*/
Current_handle = 0;
for (i = 0; i < MAX_HANDLES; i++) {
IOE_handles[i].devpollfd = -1;
IOE_handles[i].pfd = NULL;
}
/*
* this tells me the max number of open filedescriptors
*/
Max_index = sysconf(_SC_OPEN_MAX);
if (Max_index < 0) {
Max_index = 1024;
}
DBGMSG(("got sysconf(_SC_OPEN_MAX)=%d file desc\n",Max_index));
return 0;
}
JNIEXPORT jint JNICALL Java_Poller_getNumCPUs(JNIEnv *env, jclass cls)
{
return sysconf(_SC_NPROCESSORS_ONLN);
}
/*
* Class: Poller
* Method: nativeCreatePoller
* Signature: (I)I
* Note : in the case where /dev/poll doesn't exist,
* using more than one poll array could hurt
* Solaris 7 performance due to kernel caching.
*/
JNIEXPORT jint JNICALL Java_Poller_nativeCreatePoller
(JNIEnv *env, jobject obj, jint maximum_fds)
{
int handle, retval, i;
ioevent_t *ioeh;
if (maximum_fds == -1) {
maximum_fds = Max_index;
}
handle = Current_handle;
if (Current_handle >= MAX_HANDLES) {
for (i = 0; i < MAX_HANDLES; i++) {
if (IOE_handles[i].inuse == 0) {
handle = i;
break;
}
}
if (handle >= MAX_HANDLES) {
return MEMORY_EXCEPTION("CreatePoller - MAX_HANDLES exceeded");
}
} else {
Current_handle++;
}
ioeh = &IOE_handles[handle];
ioeh->inuse = 1;
ioeh->last_index = 0;
ioeh->total_free = 0;
ioeh->left_events = 0;
ioeh->max_index = maximum_fds;
retval = handle;
if (Use_devpoll) {
ioeh->devpollfd = open("/dev/poll",O_RDWR);
DBGMSG(("Opened /dev/poll, set devpollfd = %d\n",ioeh->devpollfd));
if (ioeh->devpollfd < 0) {
Current_handle--;
return MEMORY_EXCEPTION("CreatePoller - can\'t open /dev/poll");
}
}
ioeh->pfd = malloc(maximum_fds * sizeof(pollfd_t));
if (ioeh->pfd == NULL) {
Current_handle--;
return MEMORY_EXCEPTION("CreatePoller - malloc failure");
}
return retval;
}
/*
* Class: Poller
* Method: nativeDestroyPoller
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_Poller_nativeDestroyPoller
(JNIEnv *env, jobject obj, jint handle)
{
ioevent_t *ioeh;
if (handle < 0 || handle >= MAX_HANDLES)
{
STATE_EXCEPTION("DestroyPoller - handle out of range");
return;
}
ioeh = &IOE_handles[handle];
ioeh->inuse = 0;
if (Use_devpoll) {
close(ioeh->devpollfd);
}
free(ioeh->pfd);
}
#ifdef DEBUG
static void check_handle(ioevent_t *ioeh)
{
int i,used,unused;
used=unused=0;
for (i = 0; i < ioeh->last_index; i++)
{
if (ioeh->pfd[i].fd == -1)
unused++;
else
used++;
}
if (unused != ioeh->total_free)
printf("WARNING : found %d free, claimed %d. Used : %d\n",
unused, ioeh->total_free, used);
}
#endif
/*
* Class: Poller
* Method: nativeAddFd
* Signature: (IIS)I
*
* Currently doesn't check to make sure we aren't adding
* an fd already added (no problem for /dev/poll...just
* an array waster for poll()).
*/
JNIEXPORT jint JNICALL Java_Poller_nativeAddFd
(JNIEnv *env, jobject obj, jint handle, jint fd, jshort events)
{
int retval;
ioevent_t *ioeh;
if (handle < 0 || handle >= MAX_HANDLES)
return STATE_EXCEPTION("AddFd - handle out of range");
ioeh = &IOE_handles[handle];
CHECK_HANDLE(ioeh);
#ifdef DEVPOLL
if (Use_devpoll)
{
int i;
pollfd_t pollelt;
/*
* use /dev/poll
*/
pollelt.fd = fd;
pollelt.events = events;
if ((i = write(ioeh->devpollfd, &pollelt, sizeof(pollfd_t))) !=
sizeof(pollfd_t)) {
DBGMSG(("write to devpollfd=%d showed %d bytes out of %d\n",
ioeh->devpollfd,i,sizeof(pollfd_t)));
return STATE_EXCEPTION("AddFd - /dev/poll add failure");
}
else
{
retval = fd;
}
}
else
#endif
{ /* no /dev/poll available */
retval = addfd(env, ioeh, fd, events);
}
return retval;
}
/*
* Addfd to pollfd array...optimized for Solaris 7
*/
jint addfd(JNIEnv *env, ioevent_t *ioeh, jint fd, jshort events)
{
int idx;
if (ioeh->total_free)
{
/*
* Traversing from end because that's where we pad.
*/
ioeh->total_free--;
for (idx = ioeh->last_index - 1; idx >= 0; idx--) {
if (ioeh->pfd[idx].fd == -1)
break;
}
}
else if (ioeh->last_index >= ioeh->max_index)
{
return MEMORY_EXCEPTION("AddFd - too many fds");
}
else
{
int i;
int new_total;
/*
* For Solaris 7, want to add some growth space
* and fill extras with fd=-1. This allows for
* kernel poll() implementation to perform optimally.
*/
new_total = ioeh->last_index;
new_total += (new_total/10) + 1; /* bump size by 10% */
if (new_total > ioeh->max_index)
new_total = ioeh->max_index;
for (i = ioeh->last_index; i <= new_total; i++)
{
ioeh->pfd[i].fd = -1;
}
idx = ioeh->last_index;
ioeh->total_free = new_total - ioeh->last_index - 1;
DBGMSG(("Just grew from %d to %d in size\n",
ioeh->last_index, new_total));
ioeh->last_index = new_total;
}
ASSERT((idx >= 0) && (idx <= ioeh->max_index));
ASSERT(ioeh->pfd[idx].fd == -1);
ioeh->pfd[idx].fd = fd;
ioeh->pfd[idx].events = events;
ioeh->pfd[idx].revents = 0;
CHECK_HANDLE(ioeh);
return fd;
}
/*
* Class: Poller
* Method: nativeRemoveFd
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_Poller_nativeRemoveFd
(JNIEnv *env, jobject obj, jint handle, jint fd)
{
ioevent_t *ioeh;
if (handle < 0 || handle >= MAX_HANDLES)
return STATE_EXCEPTION("RemoveFd - handle out of range");
ioeh = &IOE_handles[handle];
#ifdef DEVPOLL
if (Use_devpoll)
{
/*
* use /dev/poll - currently no need for locking here.
*/
pollfd_t pollelt;
pollelt.fd = fd;
pollelt.events = POLLREMOVE;
if (write(ioeh->devpollfd, &pollelt,
sizeof(pollfd_t) ) != sizeof(pollfd_t))
{
return STATE_EXCEPTION("RemoveFd - /dev/poll failure");
}
}
else
#endif DEVPOLL
{
return removefd(env, ioeh,fd);
}
}
/*
* remove from pollfd array...optimize for Solaris 7
*/
jint removefd(JNIEnv *env, ioevent_t *ioeh, jint fd)
{
int i;
int found = 0;
{ /* !Use_devpoll */
for (i = 0; i < ioeh->last_index; i++)
{
if (ioeh->pfd[i].fd == fd)
{
ioeh->pfd[i].fd = -1;
found = 1;
break;
}
}
if (!found)
{
return STATE_EXCEPTION("RemoveFd - no such fd");
}
ioeh->left_events = 0; /* Have to go back to the kernel */
ioeh->total_free++;
/*
* Shrinking pool if > 33% empty. Just don't do this often!
*/
if ( (ioeh->last_index > 100) &&
(ioeh->total_free > (ioeh->last_index / 3)) )
{
int j;
/*
* we'll just bite the bullet here, since we're > 33% empty.
* walk through and eliminate -1 fd values, shrink total
* size to still have ~ 10 fd==-1 values at end.
* Start at end (since we pad here) and, when we find fd != -1,
* swap with an earlier fd == -1 until we have all -1 values
* at the end.
*/
CHECK_HANDLE(ioeh);
for (i = ioeh->last_index - 1, j = 0; i > j; i--)
{
if (ioeh->pfd[i].fd != -1)
{
while ( (j < i) && (ioeh->pfd[j].fd != -1) )
j++;
DBGMSG( ("i=%d,j=%d,ioeh->pfd[j].fd=%d\n",
i, j, ioeh->pfd[j].fd) );
if (j < i)
{
ASSERT(ioeh->pfd[j].fd == -1);
ioeh->pfd[j].fd = ioeh->pfd[i].fd;
ioeh->pfd[j].events = ioeh->pfd[i].events;
ioeh->pfd[i].fd = -1;
}
}
}
DBGMSG(("Just shrunk from %d to %d in size\n",
ioeh->last_index, j+11));
ioeh->last_index = j + 11; /* last_index always 1 greater */
ioeh->total_free = 10;
CHECK_HANDLE(ioeh);
}
} /* !Use_devpoll */
return 1;
}
/*
* Class: Poller
* Method: nativeIsMember
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_Poller_nativeIsMember
(JNIEnv *env, jobject obj, jint handle, jint fd)
{
int found = 0;
int i;
ioevent_t *ioeh;
if (handle < 0 || handle >= MAX_HANDLES)
return STATE_EXCEPTION("IsMember - handle out of range");
ioeh = &IOE_handles[handle];
#ifdef DEVPOLL
if (Use_devpoll)
{
pollfd_t pfd;
/*
* DEVPOLL ioctl DP_ISPOLLED call to determine if fd is polled.
*/
pfd.fd = fd;
pfd.events = 0;
pfd.revents = 0;
found = ioctl(ioeh->devpollfd, DP_ISPOLLED, &pfd);
if (found == -1)
{
return STATE_EXCEPTION("IsMember - /dev/poll failure");
}
}
else
#endif
{
for (i = 0; i < ioeh->last_index; i++)
{
if (fd == ioeh->pfd[i].fd)
{
found = 1;
break;
}
}
}
return found;
}
/*
* Class: Poller
* Method: nativeWait
* Signature: (II[I[SJ)I
*/
JNIEXPORT jint JNICALL Java_Poller_nativeWait
(JNIEnv *env, jobject obj, jint handle, jint maxEvents,
jintArray jfds, jshortArray jrevents, jlong timeout)
{
int useEvents, count, idx;
short *reventp;
jint *fdp;
int retval;
ioevent_t *ioeh;
jboolean isCopy1,isCopy2;
if (handle < 0 || handle >= MAX_HANDLES)
return STATE_EXCEPTION("nativeWait - handle out of range");
ioeh = &IOE_handles[handle];
if (maxEvents == 0) /* just doing a kernel delay! */
{
useEvents = poll(NULL,0L,timeout);
return 0;
}
#ifdef DEVPOLL
if (Use_devpoll)
{
struct dvpoll dopoll;
/*
* DEVPOLL ioctl DP_POLL call, reading
*/
dopoll.dp_timeout = timeout;
dopoll.dp_nfds=maxEvents;
dopoll.dp_fds=ioeh->pfd;
useEvents = ioctl(ioeh->devpollfd, DP_POLL, &dopoll);
while ((useEvents == -1) && (errno == EAGAIN))
useEvents = ioctl(ioeh->devpollfd, DP_POLL, &dopoll);
if (useEvents == -1)
{
if (errno == EINTR)
return INTERRUPT_EXCEPTION("nativeWait - /dev/poll failure EINTR");
else
return STATE_EXCEPTION("nativeWait - /dev/poll failure");
}
reventp =(*env)->GetShortArrayElements(env,jrevents,&isCopy1);
fdp =(*env)->GetIntArrayElements(env,jfds,&isCopy2);
for (idx = 0,count = 0; idx < useEvents; idx++)
{
if (ioeh->pfd[idx].revents)
{
fdp[count] = ioeh->pfd[idx].fd;
reventp[count] = ioeh->pfd[idx].revents;
count++;
}
}
if (count < useEvents)
return STATE_EXCEPTION("Wait - Corrupted internals");
if (isCopy1 == JNI_TRUE)
(*env)->ReleaseShortArrayElements(env,jrevents,reventp,0);
if (isCopy2 == JNI_TRUE)
(*env)->ReleaseIntArrayElements(env,jfds,fdp,0);
}
else
#endif
{ /* !Use_devpoll */
/* no leftovers=>go to kernel */
if (ioeh->left_events == 0)
{
useEvents = poll(ioeh->pfd,ioeh->last_index, timeout);
while ((useEvents == -1) && (errno == EAGAIN))
useEvents = poll(ioeh->pfd,ioeh->last_index, timeout);
if (useEvents == -1)
{
if (errno == EINTR)
return INTERRUPT_EXCEPTION("Wait - poll() failure EINTR-" \
"IO interrupted.");
else if (errno == EINVAL)
return STATE_EXCEPTION("Wait - poll() failure EINVAL-" \
"invalid args (is fdlim cur < max?)");
else
return STATE_EXCEPTION("Wait - poll() failure");
}
ioeh->left_events = useEvents;
DBGMSG(("waitnative : poll returns : %d\n",useEvents));
}
else
{ /* left over from last call */
useEvents = ioeh->left_events;
}
if (useEvents > maxEvents)
{
useEvents = maxEvents;
}
ioeh->left_events -= useEvents; /* left to process */
DBGMSG(("waitnative : left %d, use %d, max %d\n",ioeh->left_events,
useEvents,maxEvents));
if (useEvents > 0)
{
reventp =(*env)->GetShortArrayElements(env,jrevents,&isCopy1);
fdp =(*env)->GetIntArrayElements(env,jfds,&isCopy2);
for (idx = 0,count = 0; (idx < ioeh->last_index) &&
(count < useEvents); idx++)
{
if (ioeh->pfd[idx].revents)
{
fdp[count] = ioeh->pfd[idx].fd;
reventp[count] = ioeh->pfd[idx].revents;
/* in case of leftover for next walk */
ioeh->pfd[idx].revents = 0;
count++;
}
}
if (count < useEvents)
{
ioeh->left_events = 0;
return STATE_EXCEPTION("Wait - Corrupted internals");
}
if (isCopy1 == JNI_TRUE)
(*env)->ReleaseShortArrayElements(env,jrevents,reventp,0);
if (isCopy2 == JNI_TRUE)
(*env)->ReleaseIntArrayElements(env,jfds,fdp,0);
}
} /* !Use_devpoll */
return useEvents;
}