Just to complete this thread, here is the version we ended up with.
Changes since last time are that checks for errors on the ioctl for
setting non-blocking mode; removes the local variable "lbolt" entirely;
avoids the final call to ksocket_connect if the callback occurs;
correctly initializes the memory for the structure used for
communication with the callback; and shuts down the socket on connect
timeout to leave the socket in a known state. Thanks to all for your
help with this.
/* used by idm_so_timed_socket_connect */
typedef struct idm_so_timed_socket_s {
kcondvar_t it_cv;
boolean_t it_callback_called;
int it_socket_error_code;
} idm_so_timed_socket_t;
/* Called by kernel sockets when the connection has been accepted or
* rejected.
* In early volo, a "disconnect" was sent instead of"connectfailed",
* so we check for both.
/* ARGSUSED */
void *
idm_so_timed_socket_connect_cb(ksocket_t ks,
ksocket_callback_event_t ev, void *arg, uintptr_t info)
{
idm_so_timed_socket_t *itp = (idm_so_timed_socket_t *)arg;
ASSERT(itp != NULL);
ASSERT(ev == KSOCKET_EV_CONNECTED ||
ev == KSOCKET_EV_CONNECTFAILED ||
ev == KSOCKET_EV_DISCONNECTED);
mutex_enter(&idm_so_timed_socket_mutex);
itp->it_callback_called = B_TRUE;
if (ev == KSOCKET_EV_CONNECTED)
itp->it_callback_error_code = 0;
else {
/* Make sure the error code is non-zero on error */
if (info == 0)
info = ECONNRESET;
itp->it_socket_error_code = (int)info;
}
cv_signal(&itp->it_cv);
mutex_exit(&idm_so_timed_socket_mutex);
return (NULL);
}
int
idm_so_timed_socket_connect(ksocket_t ks,
struct sockaddr_storage *sa, int sa_sz, int login_max_usec)
{
clock_t conn_login_max;
int rc, nonblocking, rval;
idm_so_timed_socket_t it;
ksocket_callbacks_t ks_cb;
conn_login_max = ddi_get_lbolt() + drv_usectohz(login_max_usec);
/*
* Set to non-block socket mode, with callback on connect
* Early volo used "disconnected" instead of "connectfailed",
* so set callback to look for both.
*/
bzero(&it, sizeof (it));
ks_cb.ksock_cb_flags = KSOCKET_CB_CONNECTED |
KSOCKET_CB_CONNECTFAILED | KSOCKET_CB_DISCONNECTED;
ks_cb.ksock_cb_connected =
(ksocket_callback_t)idm_so_timed_socket_connect_cb;
ks_cb.ksock_cb_connectfailed =
(ksocket_callback_t)idm_so_timed_socket_connect_cb;
ks_cb.ksock_cb_disconnected =
(ksocket_callback_t)idm_so_timed_socket_connect_cb;
cv_init(&it.it_cv, NULL, CV_DEFAULT, NULL);
rc = ksocket_setcallbacks(ks, &ks_cb, (void *)&it, CRED());
if (rc != 0)
return (rc);
/* Set to non-blocking mode */
nonblocking = 1;
rc = ksocket_ioctl(ks, FIONBIO, (intptr_t)&nonblocking, &rval,
CRED());
if (rc != 0)
goto cleanup;
bzero(&it, sizeof (it));
for (;;) {
/*
* Warning -- in a loopback scenario, the call to
* the connect_cb can occur inside the call to
* ksocket_connect. Do not hold the mutex around the
* call to ksocket_connect.
*/
rc = ksocket_connect(ks, (struct sockaddr *)sa, sa_sz, CRED());
if (rc == 0 || rc == EISCONN) {
/* socket success or already success */
rc = 0;
break;
}
if ((rc != EINPROGRESS) && (rc != EALREADY)) {
break;
}
/* TCP connect still in progress. See if out of time. */
if (ddi_get_lbolt() > conn_login_max) {
/*
* Connection retry timeout,
* failed connect to target.
*/
rc = ETIMEDOUT;
break;
}
/*
* TCP connect still in progress. Sleep until callback.
* Do NOT go to sleep if the callback already occurred!
*/
mutex_enter(&idm_so_timed_socket_mutex);
if (!it.it_callback_called) {
(void) cv_timedwait(&it.it_cv,
&idm_so_timed_socket_mutex, conn_login_max);
}
if (it.it_callback_called) {
rc = it.it_socket_error_code;
mutex_exit(&idm_so_timed_socket_mutex);
break;
}
/* If timer expires, go call ksocket_connect one last time. */
mutex_exit(&idm_so_timed_socket_mutex);
}
/* resume blocking mode */
nonblocking = 0;
(void) ksocket_ioctl(ks, FIONBIO, (intptr_t)&nonblocking, &rval,
CRED());
cleanup:
ksocket_setcallbacks(ks, NULL, NULL, CRED());
cv_destroy(&it.it_cv);
if (rc != 0) {
idm_soshutdown(ks);
}
return (rc);
}
On 09/01/09 22:21, Peter Cudhea wrote:
Thanks for your comments. I decided to keep the mutex because that
is what cv_timedwait knows how to deal with. But I no longer hold the
mutex over the call to ksocket_connect. So that should eliminate the
possible recursive mutex call that you were worried about.
Here is what I ended up with. I have tested this with successful
connects, connects that time out, and connects that are immediately
rejected.
I probably don't need the variable "final_pass". If the socket
callback is called, then ksocket_connect should then return either
success or some definite error. But I felt safer writing that
assumption into the code.
/* used by idm_so_timed_socket_connect */
typedef struct idm_so_timed_socket_s {
kcondvar_t it_cv;
boolean_t it_callback_called;
} idm_so_timed_socket_t;
/* ARGSUSED */
void *
idm_so_timed_socket_connect_cb(ksocket_t ks,
ksocket_callback_event_t ev, void *arg, uintptr_t info)
{
idm_so_timed_socket_t *itp =
(idm_so_timed_socket_t *)arg;
ASSERT(itp != NULL);
ASSERT(ev == KSOCKET_EV_CONNECTED ||
ev == KSOCKET_EV_CONNECTFAILED ||
ev == KSOCKET_EV_DISCONNECTED);
mutex_enter(&idm_so_timed_socket_mutex);
itp->it_callback_called = B_TRUE;
cv_signal(&itp->it_cv);
mutex_exit(&idm_so_timed_socket_mutex);
return (NULL);
}
int
idm_so_timed_socket_connect(ksocket_t ks,
struct sockaddr_storage *sa, int sa_sz, int login_max_usec)
{
clock_t conn_login_max, lbolt;
int rc, nonblocking, rval;
idm_so_timed_socket_t it;
ksocket_callbacks_t ks_cb;
boolean_t final_pass = B_FALSE;
conn_login_max = ddi_get_lbolt() +
drv_usectohz(login_max_usec);
/*
Set to non-block socket mode, with callback on connect
Early volo used "disconnected" instead of "connectfailed",
so set callback to look for both.
*/
bzero(&it, sizeof (it));
ks_cb.ksock_cb_flags = KSOCKET_CB_CONNECTED |
KSOCKET_CB_CONNECTFAILED | KSOCKET_CB_DISCONNECTED;
ks_cb.ksock_cb_connected =
(ksocket_callback_t)idm_so_timed_socket_connect_cb;
ks_cb.ksock_cb_connectfailed =
(ksocket_callback_t)idm_so_timed_socket_connect_cb;
ks_cb.ksock_cb_disconnected =
(ksocket_callback_t)idm_so_timed_socket_connect_cb;
cv_init(&it.it_cv, NULL, CV_DEFAULT, NULL);
(void) ksocket_setcallbacks(ks, &ks_cb, (void *)&it, CRED());
/* Set to non-blocking mode */
nonblocking = 1;
(void) ksocket_ioctl(ks, FIONBIO, (intptr_t)&nonblocking, &rval,
CRED());
for (;;) {
/*
* Warning -- in a loopback scenario, the call to
* the connect_cb can occur inside the call to
* ksocket_connect. Do not hold the mutex around the
* call to ksocket_connect.
*/
rc = ksocket_connect(ks, (struct sockaddr *)sa, sa_sz,
CRED());
if (rc == 0 || rc == EISCONN) {
/* socket success or already success */
rc = 0;
break;
}
if ((rc != EINPROGRESS) && (rc != EALREADY)) {
break;
}
lbolt = ddi_get_lbolt();
if (lbolt > conn_login_max || final_pass) {
/*
* Connection retry timeout,
* failed connect to target.
*/
rc = ETIMEDOUT;
break;
}
/* TCP connect still in progress. Sleep until
callback. */
mutex_enter(&idm_so_timed_socket_mutex);
if (! it.it_callback_called) {
(void) cv_timedwait(&it.it_cv,
&idm_so_timed_socket_mutex, conn_login_max);
} else {
final_pass = B_TRUE;
}
mutex_exit(&idm_so_timed_socket_mutex);
}
/* resume blocking mode */
nonblocking = 0;
(void) ksocket_ioctl(ks, FIONBIO, (intptr_t)&nonblocking, &rval,
CRED());
ksocket_setcallbacks(ks, NULL, NULL, CRED());
cv_destroy(&it.it_cv);
return (rc);
}
_______________________________________________
networking-discuss mailing list
[email protected]
_______________________________________________
networking-discuss mailing list
[email protected]