mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2025-02-02 15:07:36 -05:00
fix a busy CPU case when slow and fast file serving clients are connected at
the same time. Flag clients on return from select/poll and only process those. Also fix a rare race which could leave clients in pending svn path=/icecast/trunk/icecast/; revision=8070
This commit is contained in:
parent
17537d2457
commit
80561957f0
374
src/fserve.c
374
src/fserve.c
@ -60,22 +60,22 @@
|
|||||||
#define MIMETYPESFILE "/etc/mime.types"
|
#define MIMETYPESFILE "/etc/mime.types"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static avl_tree *client_tree;
|
static fserve_t *active_list = NULL;
|
||||||
static avl_tree *pending_tree;
|
static volatile fserve_t *pending_list = NULL;
|
||||||
|
|
||||||
|
static mutex_t pending_lock;
|
||||||
static avl_tree *mimetypes = NULL;
|
static avl_tree *mimetypes = NULL;
|
||||||
|
|
||||||
static cond_t fserv_cond;
|
|
||||||
static thread_type *fserv_thread;
|
static thread_type *fserv_thread;
|
||||||
static int run_fserv;
|
static int run_fserv = 0;
|
||||||
static int fserve_clients;
|
static unsigned int fserve_clients;
|
||||||
static int client_tree_changed=0;
|
static int client_tree_changed=0;
|
||||||
|
|
||||||
#ifdef HAVE_POLL
|
#ifdef HAVE_POLL
|
||||||
static struct pollfd *ufds = NULL;
|
static struct pollfd *ufds = NULL;
|
||||||
static int ufdssize = 0;
|
|
||||||
#else
|
#else
|
||||||
static fd_set fds;
|
static fd_set fds;
|
||||||
static int fd_max = 0;
|
static int fd_max = -1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -83,9 +83,6 @@ typedef struct {
|
|||||||
char *type;
|
char *type;
|
||||||
} mime_type;
|
} mime_type;
|
||||||
|
|
||||||
/* avl tree helper */
|
|
||||||
static int _compare_clients(void *compare_arg, void *a, void *b);
|
|
||||||
static int _remove_client(void *key);
|
|
||||||
static int _free_client(void *key);
|
static int _free_client(void *key);
|
||||||
static int _delete_mapping(void *mapping);
|
static int _delete_mapping(void *mapping);
|
||||||
static void *fserv_thread_function(void *arg);
|
static void *fserv_thread_function(void *arg);
|
||||||
@ -103,9 +100,7 @@ void fserve_initialize(void)
|
|||||||
|
|
||||||
create_mime_mappings(MIMETYPESFILE);
|
create_mime_mappings(MIMETYPESFILE);
|
||||||
|
|
||||||
client_tree = avl_tree_new(_compare_clients, NULL);
|
thread_mutex_create (&pending_lock);
|
||||||
pending_tree = avl_tree_new(_compare_clients, NULL);
|
|
||||||
thread_cond_create(&fserv_cond);
|
|
||||||
|
|
||||||
run_fserv = 1;
|
run_fserv = 1;
|
||||||
|
|
||||||
@ -115,207 +110,205 @@ void fserve_initialize(void)
|
|||||||
|
|
||||||
void fserve_shutdown(void)
|
void fserve_shutdown(void)
|
||||||
{
|
{
|
||||||
ice_config_t *config = config_get_config();
|
|
||||||
int serve = config->fileserve;
|
|
||||||
|
|
||||||
config_release_config();
|
|
||||||
|
|
||||||
if(!serve)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(!run_fserv)
|
if(!run_fserv)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
avl_tree_free(mimetypes, _delete_mapping);
|
|
||||||
|
|
||||||
run_fserv = 0;
|
run_fserv = 0;
|
||||||
thread_cond_signal(&fserv_cond);
|
|
||||||
thread_join(fserv_thread);
|
thread_join(fserv_thread);
|
||||||
|
INFO0("file serving thread stopped");
|
||||||
thread_cond_destroy(&fserv_cond);
|
avl_tree_free(mimetypes, _delete_mapping);
|
||||||
avl_tree_free(client_tree, _free_client);
|
|
||||||
avl_tree_free(pending_tree, _free_client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wait_for_fds() {
|
|
||||||
avl_node *client_node;
|
|
||||||
fserve_t *client;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
while(run_fserv) {
|
|
||||||
#ifdef HAVE_POLL
|
#ifdef HAVE_POLL
|
||||||
if(client_tree_changed) {
|
int fserve_client_waiting (void)
|
||||||
client_tree_changed = 0;
|
{
|
||||||
i = 0;
|
fserve_t *fclient;
|
||||||
ufdssize = fserve_clients;
|
unsigned int i = 0;
|
||||||
ufds = realloc(ufds, ufdssize * sizeof(struct pollfd));
|
|
||||||
avl_tree_rlock(client_tree);
|
|
||||||
client_node = avl_get_first(client_tree);
|
|
||||||
while(client_node) {
|
|
||||||
client = client_node->key;
|
|
||||||
ufds[i].fd = client->client->con->sock;
|
|
||||||
ufds[i].events = POLLOUT;
|
|
||||||
client_node = avl_get_next(client_node);
|
|
||||||
}
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(poll(ufds, ufdssize, 200) > 0)
|
/* only rebuild ufds if there are clients added/removed */
|
||||||
return;
|
if (client_tree_changed)
|
||||||
|
{
|
||||||
|
client_tree_changed = 0;
|
||||||
|
ufds = realloc(ufds, fserve_clients * sizeof(struct pollfd));
|
||||||
|
fclient = active_list;
|
||||||
|
while (fclient)
|
||||||
|
{
|
||||||
|
ufds[i].fd = fclient->client->con->sock;
|
||||||
|
ufds[i].events = POLLOUT;
|
||||||
|
ufds[i].revents = 0;
|
||||||
|
fclient = fclient->next;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (poll(ufds, fserve_clients, 200) > 0)
|
||||||
|
{
|
||||||
|
/* mark any clients that are ready */
|
||||||
|
fclient = active_list;
|
||||||
|
for (i=0; i<fserve_clients; i++)
|
||||||
|
{
|
||||||
|
if (ufds[i].revents & (POLLOUT|POLLHUP|POLLERR))
|
||||||
|
fclient->ready = 1;
|
||||||
|
fclient = fclient->next;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
|
int fserve_client_waiting (void)
|
||||||
|
{
|
||||||
|
fserve_t *fclient;
|
||||||
|
fd_set realfds;
|
||||||
|
|
||||||
|
/* only rebuild fds if there are clients added/removed */
|
||||||
|
if(client_tree_changed) {
|
||||||
|
client_tree_changed = 0;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
fd_max = -1;
|
||||||
|
fclient = active_list;
|
||||||
|
while (fclient) {
|
||||||
|
FD_SET (fclient->client->con->sock, &fds);
|
||||||
|
if (fclient->client->con->sock > fd_max)
|
||||||
|
fd_max = fclient->client->con->sock;
|
||||||
|
fclient = fclient->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* hack for windows, select needs at least 1 descriptor */
|
||||||
|
if (fd_max == -1)
|
||||||
|
thread_sleep (200000);
|
||||||
|
else
|
||||||
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
fd_set realfds;
|
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
tv.tv_usec = 200000;
|
tv.tv_usec = 200000;
|
||||||
if(client_tree_changed) {
|
/* make a duplicate of the set so we do not have to rebuild it
|
||||||
client_tree_changed = 0;
|
* each time around */
|
||||||
i=0;
|
|
||||||
FD_ZERO(&fds);
|
|
||||||
fd_max = 0;
|
|
||||||
avl_tree_rlock(client_tree);
|
|
||||||
client_node = avl_get_first(client_tree);
|
|
||||||
while(client_node) {
|
|
||||||
client = client_node->key;
|
|
||||||
FD_SET(client->client->con->sock, &fds);
|
|
||||||
if(client->client->con->sock > fd_max)
|
|
||||||
fd_max = client->client->con->sock;
|
|
||||||
client_node = avl_get_next(client_node);
|
|
||||||
}
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&realfds, &fds, sizeof(fd_set));
|
memcpy(&realfds, &fds, sizeof(fd_set));
|
||||||
if(select(fd_max+1, NULL, &realfds, NULL, &tv) > 0)
|
if(select(fd_max+1, NULL, &realfds, NULL, &tv) > 0)
|
||||||
return;
|
{
|
||||||
#endif
|
/* mark any clients that are ready */
|
||||||
else {
|
fclient = active_list;
|
||||||
avl_tree_rlock(pending_tree);
|
while (fclient)
|
||||||
client_node = avl_get_first(pending_tree);
|
{
|
||||||
avl_tree_unlock(pending_tree);
|
if (FD_ISSET (fclient->client->con->sock, &realfds))
|
||||||
if(client_node)
|
fclient->ready = 1;
|
||||||
return;
|
fclient = fclient->next;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void wait_for_fds() {
|
||||||
|
fserve_t *fclient;
|
||||||
|
|
||||||
|
while (run_fserv)
|
||||||
|
{
|
||||||
|
/* add any new clients here */
|
||||||
|
if (pending_list)
|
||||||
|
{
|
||||||
|
thread_mutex_lock (&pending_lock);
|
||||||
|
|
||||||
|
fclient = (fserve_t*)pending_list;
|
||||||
|
while (fclient)
|
||||||
|
{
|
||||||
|
fserve_t *to_move = fclient;
|
||||||
|
fclient = fclient->next;
|
||||||
|
to_move->next = active_list;
|
||||||
|
active_list = to_move;
|
||||||
|
client_tree_changed = 1;
|
||||||
|
fserve_clients++;
|
||||||
|
stats_event_inc(NULL, "clients");
|
||||||
|
}
|
||||||
|
pending_list = NULL;
|
||||||
|
thread_mutex_unlock (&pending_lock);
|
||||||
|
}
|
||||||
|
/* drop out of here is someone is ready */
|
||||||
|
if (fserve_client_waiting())
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *fserv_thread_function(void *arg)
|
static void *fserv_thread_function(void *arg)
|
||||||
{
|
{
|
||||||
avl_node *client_node, *pending_node;
|
fserve_t *fclient, **trail;
|
||||||
fserve_t *client;
|
|
||||||
int sbytes, bytes;
|
int sbytes, bytes;
|
||||||
|
|
||||||
|
INFO0("file serving thread started");
|
||||||
while (run_fserv) {
|
while (run_fserv) {
|
||||||
avl_tree_rlock(client_tree);
|
|
||||||
|
|
||||||
client_node = avl_get_first(client_tree);
|
|
||||||
if(!client_node) {
|
|
||||||
avl_tree_rlock(pending_tree);
|
|
||||||
pending_node = avl_get_first(pending_tree);
|
|
||||||
if(!pending_node) {
|
|
||||||
/* There are no current clients. Wait until there are... */
|
|
||||||
avl_tree_unlock(pending_tree);
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
thread_cond_wait(&fserv_cond);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
avl_tree_unlock(pending_tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This isn't hugely efficient, but it'll do for now */
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
wait_for_fds();
|
wait_for_fds();
|
||||||
|
|
||||||
avl_tree_rlock(client_tree);
|
fclient = active_list;
|
||||||
client_node = avl_get_first(client_tree);
|
trail = &active_list;
|
||||||
|
|
||||||
while(client_node) {
|
while (fclient)
|
||||||
avl_node_wlock(client_node);
|
{
|
||||||
|
/* process this client, if it is ready */
|
||||||
|
if (fclient->ready)
|
||||||
|
{
|
||||||
|
fclient->ready = 0;
|
||||||
|
if(fclient->offset >= fclient->datasize) {
|
||||||
|
/* Grab a new chunk */
|
||||||
|
bytes = fread(fclient->buf, 1, BUFSIZE, fclient->file);
|
||||||
|
if (bytes == 0)
|
||||||
|
{
|
||||||
|
fserve_t *to_go = fclient;
|
||||||
|
fclient = fclient->next;
|
||||||
|
*trail = fclient;
|
||||||
|
_free_client (to_go);
|
||||||
|
fserve_clients--;
|
||||||
|
client_tree_changed = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fclient->offset = 0;
|
||||||
|
fclient->datasize = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
client = (fserve_t *)client_node->key;
|
/* Now try and send current chunk. */
|
||||||
|
sbytes = client_send_bytes (fclient->client,
|
||||||
|
&fclient->buf[fclient->offset],
|
||||||
|
fclient->datasize - fclient->offset);
|
||||||
|
|
||||||
if(client->offset >= client->datasize) {
|
/* TODO: remove clients if they take too long. */
|
||||||
/* Grab a new chunk */
|
if(sbytes > 0) {
|
||||||
bytes = fread(client->buf, 1, BUFSIZE, client->file);
|
fclient->offset += sbytes;
|
||||||
if(bytes <= 0) {
|
}
|
||||||
client->client->con->error = 1;
|
|
||||||
avl_node_unlock(client_node);
|
if (fclient->client->con->error)
|
||||||
client_node = avl_get_next(client_node);
|
{
|
||||||
|
fserve_t *to_go = fclient;
|
||||||
|
fclient = fclient->next;
|
||||||
|
*trail = fclient;
|
||||||
|
fserve_clients--;
|
||||||
|
_free_client (to_go);
|
||||||
|
client_tree_changed = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
client->offset = 0;
|
|
||||||
client->datasize = bytes;
|
|
||||||
}
|
}
|
||||||
|
trail = &fclient->next;
|
||||||
/* Now try and send current chunk. */
|
fclient = fclient->next;
|
||||||
sbytes = client_send_bytes (client->client,
|
|
||||||
&client->buf[client->offset],
|
|
||||||
client->datasize - client->offset);
|
|
||||||
|
|
||||||
/* TODO: remove clients if they take too long. */
|
|
||||||
if(sbytes >= 0) {
|
|
||||||
client->offset += sbytes;
|
|
||||||
}
|
|
||||||
avl_node_unlock(client_node);
|
|
||||||
client_node = avl_get_next(client_node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
|
|
||||||
/* Now we need a write lock instead, to delete done clients. */
|
|
||||||
avl_tree_wlock(client_tree);
|
|
||||||
|
|
||||||
client_node = avl_get_first(client_tree);
|
|
||||||
while(client_node) {
|
|
||||||
client = (fserve_t *)client_node->key;
|
|
||||||
if(client->client->con->error) {
|
|
||||||
fserve_clients--;
|
|
||||||
client_node = avl_get_next(client_node);
|
|
||||||
avl_delete(client_tree, (void *)client, _free_client);
|
|
||||||
client_tree_changed = 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
client_node = avl_get_next(client_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
avl_tree_wlock(pending_tree);
|
|
||||||
|
|
||||||
/* And now insert new clients. */
|
|
||||||
client_node = avl_get_first(pending_tree);
|
|
||||||
while(client_node) {
|
|
||||||
client = (fserve_t *)client_node->key;
|
|
||||||
avl_insert(client_tree, client);
|
|
||||||
client_tree_changed = 1;
|
|
||||||
fserve_clients++;
|
|
||||||
stats_event_inc(NULL, "clients");
|
|
||||||
client_node = avl_get_next(client_node);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear pending */
|
|
||||||
while(avl_get_first(pending_tree)) {
|
|
||||||
avl_delete(pending_tree, avl_get_first(pending_tree)->key,
|
|
||||||
_remove_client);
|
|
||||||
}
|
|
||||||
|
|
||||||
avl_tree_unlock(pending_tree);
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Shutdown path */
|
/* Shutdown path */
|
||||||
|
thread_mutex_lock (&pending_lock);
|
||||||
|
while (pending_list)
|
||||||
|
{
|
||||||
|
fserve_t *to_go = (fserve_t *)pending_list;
|
||||||
|
pending_list = to_go->next;
|
||||||
|
_free_client (to_go);
|
||||||
|
}
|
||||||
|
thread_mutex_unlock (&pending_lock);
|
||||||
|
|
||||||
avl_tree_wlock(pending_tree);
|
while (active_list)
|
||||||
while(avl_get_first(pending_tree))
|
{
|
||||||
avl_delete(pending_tree, avl_get_first(pending_tree)->key,
|
fserve_t *to_go = active_list;
|
||||||
_free_client);
|
active_list = to_go->next;
|
||||||
avl_tree_unlock(pending_tree);
|
_free_client (to_go);
|
||||||
|
}
|
||||||
|
|
||||||
avl_tree_wlock(client_tree);
|
|
||||||
while(avl_get_first(client_tree))
|
|
||||||
avl_delete(client_tree, avl_get_first(client_tree)->key,
|
|
||||||
_free_client);
|
|
||||||
avl_tree_unlock(client_tree);
|
|
||||||
|
|
||||||
thread_exit(0);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,6 +371,7 @@ int fserve_client_create(client_t *httpclient, char *path)
|
|||||||
client->client = httpclient;
|
client->client = httpclient;
|
||||||
client->offset = 0;
|
client->offset = 0;
|
||||||
client->datasize = 0;
|
client->datasize = 0;
|
||||||
|
client->ready = 0;
|
||||||
client->buf = malloc(BUFSIZE);
|
client->buf = malloc(BUFSIZE);
|
||||||
|
|
||||||
global_lock();
|
global_lock();
|
||||||
@ -405,34 +399,14 @@ int fserve_client_create(client_t *httpclient, char *path)
|
|||||||
sock_set_blocking(client->client->con->sock, SOCK_NONBLOCK);
|
sock_set_blocking(client->client->con->sock, SOCK_NONBLOCK);
|
||||||
sock_set_nodelay(client->client->con->sock);
|
sock_set_nodelay(client->client->con->sock);
|
||||||
|
|
||||||
avl_tree_wlock(pending_tree);
|
thread_mutex_lock (&pending_lock);
|
||||||
avl_insert(pending_tree, client);
|
client->next = (fserve_t *)pending_list;
|
||||||
avl_tree_unlock(pending_tree);
|
pending_list = client;
|
||||||
|
thread_mutex_unlock (&pending_lock);
|
||||||
thread_cond_signal(&fserv_cond);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _compare_clients(void *compare_arg, void *a, void *b)
|
|
||||||
{
|
|
||||||
fserve_t *clienta = (fserve_t *)a;
|
|
||||||
fserve_t *clientb = (fserve_t *)b;
|
|
||||||
|
|
||||||
connection_t *cona = clienta->client->con;
|
|
||||||
connection_t *conb = clientb->client->con;
|
|
||||||
|
|
||||||
if (cona->id < conb->id) return -1;
|
|
||||||
if (cona->id > conb->id) return 1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _remove_client(void *key)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _free_client(void *key)
|
static int _free_client(void *key)
|
||||||
{
|
{
|
||||||
fserve_t *client = (fserve_t *)key;
|
fserve_t *client = (fserve_t *)key;
|
||||||
|
@ -15,14 +15,16 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
typedef struct
|
typedef struct _fserve_t
|
||||||
{
|
{
|
||||||
client_t *client;
|
client_t *client;
|
||||||
|
|
||||||
FILE *file;
|
FILE *file;
|
||||||
int offset;
|
int offset;
|
||||||
int datasize;
|
int datasize;
|
||||||
|
int ready;
|
||||||
unsigned char *buf;
|
unsigned char *buf;
|
||||||
|
struct _fserve_t *next;
|
||||||
} fserve_t;
|
} fserve_t;
|
||||||
|
|
||||||
void fserve_initialize(void);
|
void fserve_initialize(void);
|
||||||
|
Loading…
Reference in New Issue
Block a user