/* * Copyright (C) 2004 Christos Tsantilas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "c-icap.h" #include #include "net_io.h" #include "proc_mutex.h" #include "debug.h" #include "log.h" #include "request.h" #include "ci_threads.h" #include "proc_threads_queues.h" #include "cfg_param.h" #include #include typedef struct server_decl{ int srv_id; ci_thread_t srv_pthread; struct connections_queue *con_queue; request_t *current_req; int served_requests; int served_requests_no_reallocation; } server_decl_t; ci_thread_mutex_t threads_list_mtx; server_decl_t **threads_list=NULL; ci_thread_cond_t free_server_cond; ci_thread_mutex_t counters_mtx; struct childs_queue childs_queue; child_shared_data_t *child_data; struct connections_queue *con_queue; /*Interprocess accepting mutex ....*/ ci_proc_mutex_t accept_mutex; ci_thread_t worker_thread; TCHAR *C_ICAP_CMD=TEXT("c-icap.exe -c"); extern int KEEPALIVE_TIMEOUT; extern int MAX_SECS_TO_LINGER; extern int START_CHILDS; extern int MAX_CHILDS; extern int START_SERVERS; extern int MIN_FREE_SERVERS; extern int MAX_FREE_SERVERS; extern int MAX_REQUESTS_BEFORE_REALLOCATE_MEM; #define hard_close_connection(connection) ci_hard_close(connection->fd) #define close_connection(connection) ci_linger_close(connection->fd,MAX_SECS_TO_LINGER) #define check_for_keepalive_data(fd) ci_wait_for_data(fd,KEEPALIVE_TIMEOUT,wait_for_read) static void exit_normaly(){ int i=0; server_decl_t *srv; ci_debug_printf(1,"Suppose that all childs are allready exited...\n"); while((srv=threads_list[i])!=NULL){ if(srv->current_req){ close_connection(srv->current_req->connection); ci_request_destroy(srv->current_req); } free(srv); threads_list[i]=NULL; i++; } free(threads_list); dettach_childs_queue(&childs_queue); log_close(); } static void cancel_all_threads(){ int i=0; // ci_thread_mutex_lock(&threads_list_mtx); ci_thread_cond_broadcast(&(con_queue->queue_cond));//What about childs that serve a request? while(threads_list[i]!=NULL){ ci_debug_printf(1,"Cancel server %d, thread_id %d (%d)\n",threads_list[i]->srv_id, threads_list[i]->srv_pthread,i); ci_thread_join(threads_list[i]->srv_pthread); i++; } // ci_threadmutex_unlock(&threads_list_mtx); } server_decl_t *newthread(struct connections_queue *con_queue){ int i=0; server_decl_t *serv; serv=(server_decl_t *)malloc(sizeof(server_decl_t)); serv->srv_id=0; // serv->srv_pthread=pthread_self(); serv->con_queue=con_queue; serv->served_requests=0; serv->served_requests_no_reallocation=0; serv->current_req=NULL; return serv; } int thread_main(server_decl_t *srv){ ci_connection_t con; char clientname[CI_MAXHOSTNAMELEN+1]; int ret,request_status=0; //*********************** // thread_signals(); //************************* // srv->srv_id=getpid(); //Setting my pid ... // srv->srv_pthread=pthread_self(); for(;;){ if(child_data->to_be_killed){ ci_debug_printf(1,"Thread Exiting.....\n"); return 1; //Exiting thread..... } if((ret=get_from_queue(con_queue,&con))==0){ wait_for_queue(con_queue); //It is better that the wait_for_queue to be //moved into the get_from_queue continue; } if(ret<0){ //An error has occured ci_debug_printf(1,"Error getting from connections queue\n"); break; } ci_netio_init(con.fd); ret=1; if(srv->current_req==NULL) srv->current_req=newrequest(&con); else ret=recycle_request(srv->current_req,&con); if(srv->current_req==NULL || ret==0){ ci_sockaddr_t_to_host(&(con.claddr),clientname, CI_MAXHOSTNAMELEN); ci_debug_printf(1,"Request from %s denied...\n",clientname); hard_close_connection((&con)); continue;/*The request rejected. Log an error and continue ...*/ } ci_thread_mutex_lock(&counters_mtx); (child_data->freeservers)--; (child_data->usedservers)++; ci_thread_mutex_unlock(&counters_mtx); do{ if((request_status=process_request(srv->current_req))<0){ ci_debug_printf(1,"Process request timeout or interupted....\n"); break;// } srv->served_requests++; srv->served_requests_no_reallocation++; /*Increase served requests. I dont like this. The delay is small but I don't like...*/ ci_thread_mutex_lock(&counters_mtx); (child_data->requests)++; ci_thread_mutex_unlock(&counters_mtx); log_access(srv->current_req,request_status); // break; //No keep-alive ...... if(child_data->to_be_killed) return 1; //Exiting thread..... ci_debug_printf(1,"Keep-alive:%d\n",srv->current_req->keepalive); if(srv->current_req->keepalive && check_for_keepalive_data(srv->current_req->connection->fd)){ ci_request_reset(srv->current_req); ci_debug_printf(1,"Server %d going to serve new request from client(keep-alive) \n", srv->srv_id); } else break; }while(1); if(srv->current_req){ if(request_status<0) hard_close_connection(srv->current_req->connection); else close_connection(srv->current_req->connection); } if(srv->served_requests_no_reallocation > MAX_REQUESTS_BEFORE_REALLOCATE_MEM){ ci_debug_printf(1,"Max requests reached, reallocate memory and buffers .....\n"); ci_request_destroy(srv->current_req); srv->current_req=NULL; srv->served_requests_no_reallocation=0; } ci_thread_mutex_lock(&counters_mtx); (child_data->freeservers)++; (child_data->usedservers)--; ci_thread_mutex_unlock(&counters_mtx); ci_thread_cond_signal(&free_server_cond); } return 1; } /*TODO: Reuse of sockets created during this function. To do this, call of DiconnectEx function needed instead of closesocket function and AcceptEx and overlapped operations must used. The connections queue needs a small redesign maybe with lists instead of connections array....*/ int worker_main( ci_socket sockfd){ ci_connection_t conn; int claddrlen=sizeof(struct sockaddr_in); // char clientname[300]; int haschild=1,jobs_in_queue=0; int pid=0,error; for(;;){ //Global for if(!ci_proc_mutex_lock(&accept_mutex)){ if(child_data->to_be_killed) return 1; continue; } child_data->idle=0; pid=(int)child_data->pid; ci_debug_printf(1,"Child %d getting requests now ...\n",pid); do{//Getting requests while we have free servers..... ci_debug_printf(1,"In accept loop..................\n"); error = 0; if(((conn.fd = accept(sockfd, (struct sockaddr *)&(conn.claddr.sockaddr), &claddrlen)) == INVALID_SOCKET ) && // if(((conn.fd = WSAAccept(sockfd, (struct sockaddr *)&(conn.claddr), &claddrlen,NULL,NULL)) == INVALID_SOCKET ) && (error=WSAGetLastError()) ){ ci_debug_printf(1,"error accept .... %d\nExiting server ....\n",error); exit(-1); //For the moment ....... } ci_debug_printf(1,"Accepting one connection...\n"); claddrlen=sizeof(conn.srvaddr.sockaddr); getsockname(conn.fd,(struct sockaddr *)&(conn.srvaddr.sockaddr),&claddrlen); ci_fill_sockaddr(&conn.claddr); ci_fill_sockaddr(&conn.srvaddr); icap_socket_opts(sockfd,MAX_SECS_TO_LINGER); if((jobs_in_queue=put_to_queue(con_queue,&conn))==0){ ci_debug_printf(1,"ERROR!!!!!!NO AVAILABLE SERVERS!!!!!!!!!\n"); // child_data->to_be_killed=GRACEFULLY; ci_debug_printf(1,"Jobs in Queue:%d,Free servers:%d, Used Servers :%d, Requests %d\n", jobs_in_queue, child_data->freeservers,child_data->usedservers,child_data->requests); } ci_thread_mutex_lock(&counters_mtx); haschild=(child_data->freeservers?1:0); ci_thread_mutex_unlock(&counters_mtx); (child_data->connections)++; //NUM of Requests.... }while(haschild); child_data->idle=1; ci_proc_mutex_unlock(&accept_mutex); ci_thread_mutex_lock(&counters_mtx); if(child_data->freeservers==0){ ci_debug_printf(1,"Child %d waiting for a thread to accept more connections ...\n",pid); ci_thread_cond_wait(&free_server_cond,&counters_mtx); } ci_thread_mutex_unlock(&counters_mtx); } } void child_main(ci_socket sockfd){ int claddrlen=sizeof(ci_sockaddr_t); ci_thread_t thread; int i,retcode,haschild=1,jobs_in_queue=0; int pid=0; char op; HANDLE hStdin; DWORD dwRead; // child_signals(); // pid=getpid(); hStdin = GetStdHandle(STD_INPUT_HANDLE); if ((hStdin == INVALID_HANDLE_VALUE)) ExitProcess(1); ci_thread_mutex_init(&threads_list_mtx); ci_thread_mutex_init(&counters_mtx); ci_thread_cond_init(&free_server_cond); threads_list=(server_decl_t **)malloc((START_SERVERS+1)*sizeof(server_decl_t *)); con_queue=init_queue(START_SERVERS); for(i=0;ipipe); remove_child(&childs_queue,died_child); CloseHandle(died_child); ci_thread_mutex_unlock(&control_process_mtx); } } //int check_for_died_child(struct childs_queue *childs_queue){ int check_for_died_child(DWORD msecs){ DWORD i,count,ret; // HANDLE died_child,*child_handles=malloc(sizeof(HANDLE)*childs_queue.size); HANDLE died_child,child_handles[MAXIMUM_WAIT_OBJECTS]; child_shared_data_t *ach; for(i=0,count=0;i<(DWORD)childs_queue.size;i++){ if(childs_queue.childs[i].pid!=(HANDLE)0){ child_handles[count++]=childs_queue.childs[i].pid; } if(count==MAXIMUM_WAIT_OBJECTS) break; } if(count==0){ ci_debug_printf(1,"Oups no childs! waiting for a while.....\n!"); Sleep(1000); return 0; } ci_debug_printf(1,"Objects :%d (max:%d)\n",count,MAXIMUM_WAIT_OBJECTS); ret=WaitForMultipleObjects(count,child_handles,FALSE,msecs); // ret=WaitForSingleObject(child_handles[0],msecs); if(ret==WAIT_TIMEOUT){ ci_debug_printf(1,"Operation timeout, no died child....\n"); return 0; } if(ret==WAIT_FAILED){ ci_debug_printf(1,"Wait failed. Try again!!!!!!"); return 0; } died_child=child_handles[ret]; ci_debug_printf(1,"Child with handle %d died, lets clean-up the queue\n",died_child); ach=get_child_data(&childs_queue,died_child); CloseHandle(ach->pipe); remove_child(&childs_queue,died_child); CloseHandle(died_child); return 1; } int start_server(ci_socket fd){ #ifdef MULTICHILD int child_indx,i; HANDLE child_handle; ci_thread_t mon_thread; int childs,freeservers,used,maxrequests; ci_proc_mutex_init(&accept_mutex); ci_thread_mutex_init(&control_process_mtx); if(!create_childs_queue(&childs_queue,MAX_CHILDS)){ log_server(NULL,"Can't init shared memory.Fatal error, exiting!\n"); ci_debug_printf(1,"Can't init shared memory.Fatal error, exiting!\n"); exit(0); } for(i=0;i< START_CHILDS+2; i++){ child_handle=start_child(fd); } /*Start died childs monitor thread*/ /* ci_thread_create(&mon_thread, (void *(*)(void *))wait_achild_to_die, (void *)NULL); */ while(1){ if(check_for_died_child(5000)) continue; // Sleep(5000); childs_queue_stats(&childs_queue,&childs,&freeservers, &used, &maxrequests); ci_debug_printf(1,"Server stats: \n\t Childs:%d\n\t Free servers:%d\n\tUsed servers:%d\n\tRequests served:%d\n", childs, freeservers, used, maxrequests); if( (freeservers<=MIN_FREE_SERVERS && childs=MAX_FREE_SERVERS&& childs>START_CHILDS){ ci_thread_mutex_lock(&control_process_mtx); if((child_indx=find_an_idle_child(&childs_queue))<0) continue; childs_queue.childs[child_indx].to_be_killed=GRACEFULLY; tell_child_to_die(childs_queue.childs[child_indx].pipe); ci_thread_mutex_unlock(&control_process_mtx); ci_debug_printf(1,"Going to stop child %d .....\n",childs_queue.childs[child_indx].pid); } } /* for(i=0;ipid=0; child_data->freeservers=START_SERVERS; child_data->usedservers=0; child_data->requests=0; child_data->connections=0; child_data->to_be_killed=0; child_data->idle=1; child_main(fd); #endif return 1; }