int async_fd_resume(int *fd, void *param) { async_ctx *ctx = (async_ctx *)param; int ret; async_status = ASYNC_DONE; /* assume default status as done */ /* call the resume function in order to read and handle data */ ret = ((async_resume_fd*)ctx->resume_f)( *fd, ctx->resume_param ); if (async_status==ASYNC_CONTINUE) { /* leave the fd into the reactor*/ return 0; } else if (async_status==ASYNC_CHANGE_FD) { if (ret<0) { LM_ERR("ASYNC_CHANGE_FD: given file descriptor shall be " "positive!\n"); return 0; } else if (ret>0 && ret==*fd) { /*trying to add the same fd; shall continue*/ LM_CRIT("You are trying to replace the old fd with the same fd!" "Will act as in ASYNC_CONTINUE!\n"); return 0; } /* remove the old fd from the reactor */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); *fd=ret; /* insert the new fd inside the reactor */ if (reactor_add_reader(*fd,F_FD_ASYNC,RCT_PRIO_ASYNC,(void*)ctx)<0 ) { LM_ERR("failed to add async FD to reactor -> act in sync mode\n"); do { async_status = ASYNC_DONE; ret = ((async_resume_fd*)ctx->resume_f)(*fd,ctx->resume_param); if (async_status == ASYNC_CHANGE_FD) *fd=ret; } while(async_status==ASYNC_CONTINUE||async_status==ASYNC_CHANGE_FD); goto done; } else { /* successfully changed fd */ return 0; } } /* remove from reactor, we are done */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); done: if (async_status == ASYNC_DONE_CLOSE_FD) close(*fd); return 0; }
/*! \brief releases expired connections and cleans up bad ones (state<0) */ static inline void tcp_receive_timeout(void) { struct tcp_connection* con; struct tcp_connection* next; unsigned int ticks; ticks=get_ticks(); for (con=tcp_conn_lst; con; con=next) { next=con->c_next; /* safe for removing */ if (con->state<0){ /* kill bad connections */ /* S_CONN_BAD or S_CONN_ERROR, remove it */ /* fd will be closed in tcpconn_release */ reactor_del_reader(con->fd, -1/*idx*/, IO_FD_CLOSING/*io_flags*/ ); tcpconn_listrm(tcp_conn_lst, con, c_next, c_prev); con->proc_id = -1; con->state=S_CONN_BAD; if (con->fd!=-1) { close(con->fd); con->fd = -1; } tcpconn_release(con, CONN_ERROR,0); continue; } if (con->timeout<=ticks){ LM_DBG("%p expired - (%d, %d) lt=%d\n", con, con->timeout, ticks,con->lifetime); /* fd will be closed in tcpconn_release */ reactor_del_reader(con->fd, -1/*idx*/, IO_FD_CLOSING/*io_flags*/ ); tcpconn_listrm(tcp_conn_lst, con, c_next, c_prev); /* connection is going to main */ con->proc_id = -1; if (con->fd!=-1) { close(con->fd); con->fd = -1; } if (con->msg_attempts) tcpconn_release(con, CONN_ERROR,0); else tcpconn_release(con, CONN_RELEASE,0); } } }
int async_launch_resume(int *fd, void *param) { struct sip_msg req; async_launch_ctx *ctx = (async_launch_ctx *)param; LM_DBG("resume for a launch job\n"); init_dummy_request( req ); async_status = ASYNC_DONE; /* assume default status as done */ /* call the resume function in order to read and handle data */ return_code = ((async_resume_module*)(ctx->async.resume_f)) ( *fd, &req, ctx->async.resume_param ); if (async_status==ASYNC_CONTINUE) { /* do not run the report route, leave the fd into the reactor*/ goto restore; } else if (async_status==ASYNC_DONE_NO_IO) { /* don't do any change on the fd, since the module handled everything*/ goto run_route; } else if (async_status==ASYNC_CHANGE_FD) { if (return_code<0) { LM_ERR("ASYNC_CHANGE_FD: given file descriptor must be " "positive!\n"); goto restore; } else if (return_code>0 && return_code==*fd) { /*trying to add the same fd; shall continue*/ LM_CRIT("You are trying to replace the old fd with the same fd!" "Will act as in ASYNC_CONTINUE!\n"); goto restore; } /* remove the old fd from the reactor */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); *fd=return_code; /* insert the new fd inside the reactor */ if (reactor_add_reader( *fd, F_LAUNCH_ASYNC, RCT_PRIO_ASYNC, (void*)ctx)<0 ) { LM_ERR("failed to add async FD to reactor -> act in sync mode\n"); do { async_status = ASYNC_DONE; return_code = ((async_resume_module*)(ctx->async.resume_f)) ( *fd, &req, ctx->async.resume_param ); if (async_status == ASYNC_CHANGE_FD) *fd=return_code; } while(async_status==ASYNC_CONTINUE||async_status==ASYNC_CHANGE_FD); goto run_route; } else { /* successfully changed fd */ goto restore; } } /* remove from reactor, we are done */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); run_route: if (async_status == ASYNC_DONE_CLOSE_FD) close(*fd); if (ctx->report_route!=-1) { LM_DBG("runinng report route for a launch job\n"); set_route_type( REQUEST_ROUTE ); run_top_route( rlist[ctx->report_route].a, &req); /* remove all added AVP */ reset_avps( ); } /* no need for the context anymore */ shm_free(ctx); LM_DBG("done with a launch job\n"); restore: /* clean whatever extra structures were added by script functions */ free_sip_msg(&req); return 0; }
/* function triggered from reactor in order to continue the processing */ int t_resume_async(int *fd, void *param) { static struct sip_msg faked_req; static struct ua_client uac; async_ctx *ctx = (async_ctx *)param; struct cell *backup_t; struct cell *backup_cancelled_t; struct cell *backup_e2eack_t; struct usr_avp **backup_list; struct socket_info* backup_si; struct cell *t= ctx->t; int route; LM_DBG("resuming on fd %d, transaction %p \n",*fd, t); if (current_processing_ctx) { LM_CRIT("BUG - a context already set!\n"); abort(); } /* prepare for resume route */ uac.br_flags = getb0flags( t->uas.request ) ; uac.uri = *GET_RURI( t->uas.request ); if (!fake_req( &faked_req /* the fake msg to be built*/, t->uas.request, /* the template msg saved in transaction */ &t->uas, /*the UAS side of the transaction*/ &uac, /* the fake UAC */ 1 /* copy dst_uri too */) ) { LM_ERR("fake_req failed\n"); return 0; } /* enviroment setting */ current_processing_ctx = ctx->msg_ctx; backup_t = get_t(); backup_e2eack_t = get_e2eack_t(); backup_cancelled_t = get_cancelled_t(); /* fake transaction */ set_t( t ); set_cancelled_t(ctx->cancelled_t); set_e2eack_t(ctx->e2eack_t); reset_kr(); set_kr(ctx->kr); /* make available the avp list from transaction */ backup_list = set_avp_list( &t->user_avps ); /* set default send address to the saved value */ backup_si = bind_address; bind_address = t->uac[0].request.dst.send_sock; async_status = ASYNC_DONE; /* assume default status as done */ /* call the resume function in order to read and handle data */ return_code = ctx->resume_f( *fd, &faked_req, ctx->resume_param ); if (async_status==ASYNC_CONTINUE) { /* do not run the resume route */ goto restore; } else if (async_status==ASYNC_CHANGE_FD) { if (return_code<0) { LM_ERR("ASYNC_CHANGE_FD: given file descriptor shall be positive!\n"); goto restore; } else if (return_code > 0 && return_code == *fd) { /*trying to add the same fd; shall continue*/ LM_CRIT("You are trying to replace the old fd with the same fd!" "Will act as in ASYNC_CONTINUE!\n"); goto restore; } /* remove the old fd from the reactor */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); *fd=return_code; /* insert the new fd inside the reactor */ if (reactor_add_reader( *fd, F_SCRIPT_ASYNC, RCT_PRIO_ASYNC, (void*)ctx)<0 ) { LM_ERR("failed to add async FD to reactor -> act in sync mode\n"); do { return_code = ctx->resume_f( *fd, &faked_req, ctx->resume_param ); if (async_status == ASYNC_CHANGE_FD) *fd=return_code; } while(async_status==ASYNC_CONTINUE||async_status==ASYNC_CHANGE_FD); goto route; } /* changed fd; now restore old state */ goto restore; } /* remove from reactor, we are done */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); route: if (async_status == ASYNC_DONE_CLOSE_FD) close(*fd); /* run the resume_route (some type as the original one) */ swap_route_type(route, ctx->route_type); run_resume_route( ctx->resume_route, &faked_req); set_route_type(route); /* no need for the context anymore */ shm_free(ctx); /* free also the processing ctx if still set * NOTE: it may become null if inside the run_resume_route * another async jump was made (and context attached again * to transaction) */ if (current_processing_ctx) { context_destroy(CONTEXT_GLOBAL, current_processing_ctx); pkg_free(current_processing_ctx); } restore: /* restore original environment */ set_t(backup_t); set_cancelled_t(backup_cancelled_t); set_e2eack_t(backup_e2eack_t); /* restore original avp list */ set_avp_list( backup_list ); bind_address = backup_si; free_faked_req( &faked_req, t); current_processing_ctx = NULL; return 0; }
/*! \brief handles io from a "generic" ser process (get fd or new_fd from a tcp_send) * * \param p - pointer in the ser processes array (pt[]), to the entry for * which an io event was detected * \param fd_i - fd index in the fd_array (useful for optimizing * io_watch_deletes) * \return handle_* return convention: * - -1 on error reading from the fd, * - 0 on EAGAIN or when no more io events are queued * (receive buffer empty), * - >0 on successfull reads from the fd (the receive buffer might * be non-empty). */ inline static int handle_worker(struct process_table* p, int fd_i) { struct tcp_connection* tcpconn; long response[2]; int cmd; int bytes; int ret; int fd; ret=-1; if (p->unix_sock<=0){ /* (we can't have a fd==0, 0 is never closed )*/ LM_CRIT("fd %d for %d (pid %d)\n", p->unix_sock, (int)(p-&pt[0]), p->pid); goto error; } /* get all bytes and the fd (if transmitted) * (this is a SOCK_STREAM so read is not atomic) */ bytes=receive_fd(p->unix_sock, response, sizeof(response), &fd, MSG_DONTWAIT); if (bytes<(int)sizeof(response)){ /* too few bytes read */ if (bytes==0){ /* EOF -> bad, child has died */ LM_DBG("dead child %d, pid %d" " (shutting down?)\n", (int)(p-&pt[0]), p->pid); /* don't listen on it any more */ reactor_del_reader( p->unix_sock, fd_i, 0/*flags*/); goto error; /* child dead => no further io events from it */ }else if (bytes<0){ /* EAGAIN is ok if we try to empty the buffer * e.g: SIGIO_RT overflow mode or EPOLL ET */ if ((errno!=EAGAIN) && (errno!=EWOULDBLOCK)){ LM_CRIT("read from child %d (pid %d): %s [%d]\n", (int)(p-&pt[0]), p->pid, strerror(errno), errno); ret=-1; }else{ ret=0; } /* try to ignore ? */ goto end; }else{ /* should never happen */ LM_CRIT("too few bytes received (%d)\n", bytes ); ret=0; /* something was read so there is no error; otoh if receive_fd returned less then requested => the receive buffer is empty => no more io queued on this fd */ goto end; } } ret=1; /* something was received, there might be more queued */ LM_DBG("read response= %lx, %ld, fd %d from %d (%d)\n", response[0], response[1], fd, (int)(p-&pt[0]), p->pid); cmd=response[1]; tcpconn=(struct tcp_connection*)response[0]; if (tcpconn==0){ LM_CRIT("null tcpconn pointer received from child %d (pid %d)" "%lx, %lx\n", (int)(p-&pt[0]), p->pid, response[0], response[1]) ; goto end; } switch(cmd){ case CONN_ERROR: if (!(tcpconn->flags & F_CONN_REMOVED) && (tcpconn->s!=-1)){ reactor_del_all( tcpconn->s, -1, IO_FD_CLOSING); tcpconn->flags|=F_CONN_REMOVED; } tcpconn_destroy(tcpconn); /* will close also the fd */ break; case CONN_GET_FD: /* send the requested FD */ /* WARNING: take care of setting refcnt properly to * avoid race condition */ if (send_fd(p->unix_sock, &tcpconn, sizeof(tcpconn), tcpconn->s)<=0){ LM_ERR("send_fd failed\n"); } break; case CONN_NEW: /* update the fd in the requested tcpconn*/ /* WARNING: take care of setting refcnt properly to * avoid race condition */ if (fd==-1){ LM_CRIT(" cmd CONN_NEW: no fd received\n"); break; } tcpconn->s=fd; /* add tcpconn to the list*/ tcpconn_add(tcpconn); reactor_add_reader( tcpconn->s, F_TCPCONN, RCT_PRIO_NET, tcpconn); tcpconn->flags&=~F_CONN_REMOVED; break; case ASYNC_CONNECT: /* connection is not yet linked to hash = not yet * available to the outside world */ if (fd==-1){ LM_CRIT(" cmd CONN_NEW: no fd received\n"); break; } tcpconn->s=fd; /* add tcpconn to the list*/ tcpconn_add(tcpconn); /* FIXME - now we have lifetime==default_lifetime - should we * set a shorter one when waiting for a connect ??? */ /* only maintain the socket in the IO_WATCH_WRITE watcher * while we have stuff to write - otherwise we're going to get * useless events */ reactor_add_writer( tcpconn->s, F_TCPCONN, RCT_PRIO_NET, tcpconn); tcpconn->flags&=~F_CONN_REMOVED; break; case ASYNC_WRITE: if (tcpconn->state==S_CONN_BAD){ tcpconn->lifetime=0; break; } /* must be after the de-ref*/ reactor_add_writer( tcpconn->s, F_TCPCONN, RCT_PRIO_NET, tcpconn); tcpconn->flags&=~F_CONN_REMOVED; break; default: LM_CRIT("unknown cmd %d\n", cmd); } end: return ret; error: return -1; }
/*! \brief handles io from a tcp worker process * \param tcp_c - pointer in the tcp_children array, to the entry for * which an io event was detected * \param fd_i - fd index in the fd_array (useful for optimizing * io_watch_deletes) * \return handle_* return convention: -1 on error, 0 on EAGAIN (no more * io events queued), >0 on success. success/error refer only to * the reads from the fd. */ inline static int handle_tcp_worker(struct tcp_child* tcp_c, int fd_i) { struct tcp_connection* tcpconn; long response[2]; int cmd; int bytes; if (tcp_c->unix_sock<=0){ /* (we can't have a fd==0, 0 is never closed )*/ LM_CRIT("fd %d for %d (pid %d, ser no %d)\n", tcp_c->unix_sock, (int)(tcp_c-&tcp_children[0]), tcp_c->pid, tcp_c->proc_no); goto error; } /* read until sizeof(response) * (this is a SOCK_STREAM so read is not atomic) */ bytes=recv_all(tcp_c->unix_sock, response, sizeof(response), MSG_DONTWAIT); if (bytes<(int)sizeof(response)){ if (bytes==0){ /* EOF -> bad, child has died */ LM_DBG("dead tcp worker %d (pid %d, no %d)" " (shutting down?)\n", (int)(tcp_c-&tcp_children[0]), tcp_c->pid, tcp_c->proc_no ); /* don't listen on it any more */ reactor_del_reader( tcp_c->unix_sock, fd_i, 0/*flags*/); /* eof. so no more io here, it's ok to return error */ goto error; }else if (bytes<0){ /* EAGAIN is ok if we try to empty the buffer * e.g.: SIGIO_RT overflow mode or EPOLL ET */ if ((errno!=EAGAIN) && (errno!=EWOULDBLOCK)){ LM_CRIT("read from tcp worker %ld (pid %d, no %d) %s [%d]\n", (long)(tcp_c-&tcp_children[0]), tcp_c->pid, tcp_c->proc_no, strerror(errno), errno ); }else{ bytes=0; } /* try to ignore ? */ goto end; }else{ /* should never happen */ LM_CRIT("too few bytes received (%d)\n", bytes ); bytes=0; /* something was read so there is no error; otoh if receive_fd returned less then requested => the receive buffer is empty => no more io queued on this fd */ goto end; } } LM_DBG("reader response= %lx, %ld from %d \n", response[0], response[1], (int)(tcp_c-&tcp_children[0])); cmd=response[1]; tcpconn=(struct tcp_connection*)response[0]; if (tcpconn==0){ /* should never happen */ LM_CRIT("null tcpconn pointer received from tcp child %d (pid %d):" "%lx, %lx\n", (int)(tcp_c-&tcp_children[0]), tcp_c->pid, response[0], response[1]) ; goto end; } switch(cmd){ case CONN_RELEASE: tcp_c->busy--; if (tcpconn->state==S_CONN_BAD){ tcpconn_destroy(tcpconn); break; } tcpconn_put(tcpconn); /* must be after the de-ref*/ reactor_add_reader( tcpconn->s, F_TCPCONN, RCT_PRIO_NET, tcpconn); tcpconn->flags&=~F_CONN_REMOVED; break; case ASYNC_WRITE: tcp_c->busy--; if (tcpconn->state==S_CONN_BAD){ tcpconn_destroy(tcpconn); break; } tcpconn_put(tcpconn); /* must be after the de-ref*/ reactor_add_writer( tcpconn->s, F_TCPCONN, RCT_PRIO_NET, tcpconn); tcpconn->flags&=~F_CONN_REMOVED; break; case CONN_ERROR: case CONN_DESTROY: case CONN_EOF: /* WARNING: this will auto-dec. refcnt! */ tcp_c->busy--; /* main doesn't listen on it => we don't have to delete it if (tcpconn->s!=-1) io_watch_del(&io_h, tcpconn->s, -1, IO_FD_CLOSING); */ tcpconn_destroy(tcpconn); /* closes also the fd */ break; default: LM_CRIT("unknown cmd %d from tcp reader %d\n", cmd, (int)(tcp_c-&tcp_children[0])); } end: return bytes; error: return -1; }
/*! \brief * handles an io event on one of the watched tcp connections * * \param tcpconn - pointer to the tcp_connection for which we have an io ev. * \param fd_i - index in the fd_array table (needed for delete) * \return handle_* return convention, but on success it always returns 0 * (because it's one-shot, after a succesfull execution the fd is * removed from tcp_main's watch fd list and passed to a child => * tcp_main is not interested in further io events that might be * queued for this fd) */ inline static int handle_tcpconn_ev(struct tcp_connection* tcpconn, int fd_i, int event_type) { int fd; int err; int id; unsigned int err_len; if (event_type == IO_WATCH_READ) { /* pass it to child, so remove it from the io watch list */ LM_DBG("data available on %p %d\n", tcpconn, tcpconn->s); if (reactor_del_reader(tcpconn->s, fd_i, 0)==-1) return -1; tcpconn->flags|=F_CONN_REMOVED; tcpconn_ref(tcpconn); /* refcnt ++ */ if (send2child(tcpconn,IO_WATCH_READ)<0){ LM_ERR("no children available\n"); id = tcpconn->id; TCPCONN_LOCK(id); tcpconn->refcnt--; if (tcpconn->refcnt==0){ fd=tcpconn->s; _tcpconn_rm(tcpconn); close(fd); }else tcpconn->lifetime=0; /* force expire*/ TCPCONN_UNLOCK(id); } return 0; /* we are not interested in possibly queued io events, the fd was either passed to a child, or closed */ } else { LM_DBG("connection %p fd %d is now writable\n", tcpconn, tcpconn->s); /* we received a write event */ if (tcpconn->state==S_CONN_CONNECTING) { /* we're coming from an async connect & write * let's see if we connected successfully*/ err_len=sizeof(err); if (getsockopt(tcpconn->s, SOL_SOCKET, SO_ERROR, &err, &err_len) < 0 || \ err != 0) { LM_DBG("Failed connection attempt\n"); tcpconn_ref(tcpconn); reactor_del_all(tcpconn->s, fd_i, IO_FD_CLOSING); tcpconn->flags|=F_CONN_REMOVED; tcpconn_destroy(tcpconn); return 0; } /* we successfully connected - further treat this case as if we * were coming from an async write */ tcpconn->state = S_CONN_OK; LM_DBG("Successfully completed previous async connect\n"); goto async_write; } else { /* we're coming from an async write - * just pass to child and have it write * our TCP chunks */ async_write: /* no more write events for now */ if (reactor_del_writer( tcpconn->s, fd_i, 0)==-1) return -1; tcpconn->flags|=F_CONN_REMOVED; tcpconn_ref(tcpconn); /* refcnt ++ */ if (send2child(tcpconn,IO_WATCH_WRITE)<0){ LM_ERR("no children available\n"); id = tcpconn->id; TCPCONN_LOCK(id); tcpconn->refcnt--; if (tcpconn->refcnt==0){ fd=tcpconn->s; _tcpconn_rm(tcpconn); close(fd); }else tcpconn->lifetime=0; /* force expire*/ TCPCONN_UNLOCK(id); } return 0; } } }
/* function triggered from reactor in order to continue the processing */ int t_resume_async(int fd, void *param) { static struct sip_msg faked_req; static struct ua_client uac; async_ctx *ctx = (async_ctx *)param; struct cell *backup_t; struct usr_avp **backup_list; struct socket_info* backup_si; struct cell *t= ctx->t; int route; LM_DBG("resuming on fd %d, transaction %p \n",fd, t); if (current_processing_ctx) { LM_CRIT("BUG - a context already set!\n"); abort(); } /* prepare for resume route */ uac.br_flags = getb0flags( t->uas.request ) ; uac.uri = *GET_RURI( t->uas.request ); if (!fake_req( &faked_req /* the fake msg to be built*/, t->uas.request, /* the template msg saved in transaction */ &t->uas, /*the UAS side of the transaction*/ &uac, /* the fake UAC */ 1 /* copy dst_uri too */) ) { LM_ERR("fake_req failed\n"); return 0; } /* enviroment setting */ current_processing_ctx = ctx->msg_ctx; backup_t = get_t(); /* fake transaction */ set_t( t ); reset_kr(); set_kr(ctx->kr); /* make available the avp list from transaction */ backup_list = set_avp_list( &t->user_avps ); /* set default send address to the saved value */ backup_si = bind_address; bind_address = t->uac[0].request.dst.send_sock; async_status = ASYNC_DONE; /* assume default status as done */ /* call the resume function in order to read and handle data */ return_code = ctx->resume_f( fd, &faked_req, ctx->resume_param ); if (async_status==ASYNC_CONTINUE) { /* do not run the resume route */ goto restore; } /* remove from reactor, we are done */ reactor_del_reader( fd, -1, IO_FD_CLOSING); if (async_status == ASYNC_DONE_CLOSE_FD) close(fd); /* run the resume_route (some type as the original one) */ swap_route_type(route, ctx->route_type); run_resume_route( ctx->resume_route, &faked_req); set_route_type(route); /* no need for the context anymore */ shm_free(ctx); restore: /* restore original environment */ set_t(backup_t); /* restore original avp list */ set_avp_list( backup_list ); bind_address = backup_si; free_faked_req( &faked_req, t); current_processing_ctx = NULL; return 0; }