X-Git-Url: https://git.sesse.net/?p=cubemap;a=blobdiff_plain;f=main.cpp;h=a4a4dbe83489e1e759a7c8d35f965936bf8743f7;hp=1bcc07bd1e612573e85176749febdb204e9979f3;hb=3f82e5f8ede16a712aa0bcd0913d2d8e3689742c;hpb=c2a05ee22b52922d9c80f92d29062c845ebc80c3 diff --git a/main.cpp b/main.cpp index 1bcc07b..a4a4dbe 100644 --- a/main.cpp +++ b/main.cpp @@ -8,14 +8,18 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include "acceptor.h" +#include "markpool.h" #include "metacube.h" #include "parse.h" #include "server.h" @@ -23,9 +27,6 @@ #include "input.h" #include "state.pb.h" -#define STREAM_ID "stream" -#define STREAM_URL "http://gruessi.zrh.sesse.net:4013/" - using namespace std; ServerPool *servers = NULL; @@ -36,94 +37,64 @@ void hup(int ignored) hupped = true; } -int create_server_socket(int port) -{ - int server_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); - if (server_sock == -1) { - perror("socket"); - exit(1); - } - - int one = 1; - if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) { - perror("setsockopt(SO_REUSEADDR)"); - exit(1); - } - - // We want dual-stack sockets. (Sorry, OpenBSD and Windows XP...) - int zero = 0; - if (setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1) { - perror("setsockopt(IPV6_V6ONLY)"); - exit(1); - } - - // Set as non-blocking, so the acceptor thread can notice that we want to shut it down. - if (ioctl(server_sock, FIONBIO, &one) == -1) { - perror("ioctl(FIONBIO)"); - exit(1); - } - - sockaddr_in6 addr; - memset(&addr, 0, sizeof(addr)); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(port); - - if (bind(server_sock, reinterpret_cast(&addr), sizeof(addr)) == -1) { - perror("bind"); - exit(1); - } - - if (listen(server_sock, 128) == -1) { - perror("listen"); - exit(1); - } - - return server_sock; -} - -void *acceptor_thread_run(void *arg) +struct StatsThreadParameters { + string stats_file; + int stats_interval; +}; + +void *stats_thread_run(void *arg) { - int server_sock = int(intptr_t(arg)); + const StatsThreadParameters *parms = reinterpret_cast(arg); while (!hupped) { - // Since we are non-blocking, we need to wait for the right state first. - // Wait up to 50 ms, then check hupped. - pollfd pfd; - pfd.fd = server_sock; - pfd.events = POLLIN; - - int nfds = poll(&pfd, 1, 50); - if (nfds == 0 || (nfds == -1 && errno == EAGAIN)) { - continue; - } - if (nfds == -1) { - perror("poll"); - usleep(100000); - continue; + int fd; + FILE *fp; + time_t now; + vector client_stats; + + // Open a new, temporary file. + char *filename = strdup((parms->stats_file + ".new.XXXXXX").c_str()); + fd = mkostemp(filename, O_WRONLY); + if (fd == -1) { + perror(filename); + free(filename); + goto sleep; } - sockaddr_in6 addr; - socklen_t addrlen = sizeof(addr); + fp = fdopen(fd, "w"); + if (fp == NULL) { + perror("fdopen"); + close(fd); + unlink(filename); + free(filename); + goto sleep; + } - // Get a new socket. - int sock = accept(server_sock, reinterpret_cast(&addr), &addrlen); - if (sock == -1 && errno == EINTR) { - continue; + now = time(NULL); + client_stats = servers->get_client_stats(); + for (size_t i = 0; i < client_stats.size(); ++i) { + fprintf(fp, "%s %s %d %llu\n", + client_stats[i].remote_addr.c_str(), + client_stats[i].stream_id.c_str(), + int(now - client_stats[i].connect_time), + (long long unsigned)(client_stats[i].bytes_sent)); } - if (sock == -1) { - perror("accept"); - usleep(100000); - continue; + if (fclose(fp) == EOF) { + perror("fclose"); + unlink(filename); + free(filename); + goto sleep; } - - // Set the socket as nonblocking. - int one = 1; - if (ioctl(sock, FIONBIO, &one) == -1) { - perror("FIONBIO"); - exit(1); + + if (rename(filename, parms->stats_file.c_str()) == -1) { + perror("rename"); + unlink(filename); } - // Pick a server, round-robin, and hand over the socket to it. - servers->add_client(sock); +sleep: + int left_to_sleep = parms->stats_interval; + do { + left_to_sleep = sleep(left_to_sleep); + } while (left_to_sleep > 0 && !hupped); } return NULL; } @@ -193,26 +164,62 @@ CubemapStateProto read_tempfile(int state_fd) return state; } + +// Reuse mark pools if one already exists. +MarkPool *get_mark_pool(map, MarkPool *> *mark_pools, int from, int to) +{ + pair mark_range(from, to); + if (mark_pools->count(mark_range) != 0) { + return (*mark_pools)[mark_range]; + } + + // Check if we're overlapping some other mark pool. + for (map, MarkPool *>::const_iterator mp_it = mark_pools->begin(); + mp_it != mark_pools->end(); + ++mp_it) { + int other_from = mp_it->first.first; + int other_to = mp_it->first.second; + if ((from >= other_from && from < other_to) || + (to >= other_from && to < other_to)) { + fprintf(stderr, "WARNING: Mark pool %d-%d partially overlaps with %d-%d, you may get duplicate marks.\n", + from, to, other_from, other_to); + fprintf(stderr, " Mark pools must either be completely disjunct, or completely overlapping.\n"); + } + } + + MarkPool *mark_pool = new MarkPool(from, to); + mark_pools->insert(make_pair(mark_range, mark_pool)); + return mark_pool; +} int main(int argc, char **argv) { fprintf(stderr, "\nCubemap starting.\n"); + struct timeval serialize_start; + bool is_reexec = false; + string config_filename = (argc == 1) ? "cubemap.config" : argv[1]; vector config = parse_config(config_filename); - int port = fetch_config_int(config, "port", 1, 65535); - int num_servers = fetch_config_int(config, "num_servers", 1, 20000); // Insanely high max limit. + int port = fetch_config_int(config, "port", 1, 65535, PARAMATER_MANDATORY); + int num_servers = fetch_config_int(config, "num_servers", 1, 20000, PARAMATER_MANDATORY); // Insanely high max limit. servers = new ServerPool(num_servers); + CubemapStateProto loaded_state; int server_sock = -1, old_port = -1; set deserialized_stream_ids; map deserialized_inputs; if (argc == 4 && strcmp(argv[2], "-state") == 0) { + is_reexec = true; + fprintf(stderr, "Deserializing state from previous process... "); int state_fd = atoi(argv[3]); - CubemapStateProto loaded_state = read_tempfile(state_fd); + loaded_state = read_tempfile(state_fd); + + serialize_start.tv_sec = loaded_state.serialize_start_sec(); + serialize_start.tv_usec = loaded_state.serialize_start_usec(); // Deserialize the streams. for (int i = 0; i < loaded_state.streams_size(); ++i) { @@ -220,12 +227,6 @@ int main(int argc, char **argv) deserialized_stream_ids.insert(loaded_state.streams(i).stream_id()); } - // Put back the existing clients. It doesn't matter which server we - // allocate them to, so just do round-robin. - for (int i = 0; i < loaded_state.clients_size(); ++i) { - servers->add_client_from_serialized(loaded_state.clients(i)); - } - // Deserialize the inputs. Note that we don't actually add them to any state yet. for (int i = 0; i < loaded_state.inputs_size(); ++i) { deserialized_inputs.insert(make_pair( @@ -242,6 +243,7 @@ int main(int argc, char **argv) // Find all streams in the configuration file, and create them. set expecting_stream_ids = deserialized_stream_ids; + map, MarkPool *> mark_pools; for (unsigned i = 0; i < config.size(); ++i) { if (config[i].keyword != "stream") { continue; @@ -255,6 +257,31 @@ int main(int argc, char **argv) servers->add_stream(stream_id); } expecting_stream_ids.erase(stream_id); + + // Set up marks, if so desired. + if (config[i].parameters.count("mark")) { + string mark_str = config[i].parameters["mark"]; + size_t split = mark_str.find_first_of('-'); + if (split == string::npos) { + fprintf(stderr, "WARNING: Invalid mark specification '%s' (expected 'X-Y'), ignoring.\n", + mark_str.c_str()); + continue; + } + + string from_str(mark_str.begin(), mark_str.begin() + split); + string to_str(mark_str.begin() + split + 1, mark_str.end()); + int from = atoi(from_str.c_str()); + int to = atoi(to_str.c_str()); + + if (from <= 0 || from >= 65536 || to <= 0 || to >= 65536) { + fprintf(stderr, "WARNING: Mark pool range %d-%d is outside legal range [1,65536>, ignoring.\n", + from, to); + continue; + } + + MarkPool *mark_pool = get_mark_pool(&mark_pools, from, to); + servers->set_mark_pool(stream_id, mark_pool); + } } // Warn about any servers we've lost. @@ -282,6 +309,13 @@ int main(int argc, char **argv) server_sock = create_server_socket(port); } + // See if the user wants stats. + string stats_file = fetch_config_string(config, "stats_file", PARAMETER_OPTIONAL); + int stats_interval = fetch_config_int(config, "stats_interval", 1, INT_MAX, PARAMETER_OPTIONAL, -1); + if (stats_interval != -1 && stats_file.empty()) { + fprintf(stderr, "WARNING: 'stats_interval' given, but no 'stats_file'. No statistics will be written.\n"); + } + servers->run(); pthread_t acceptor_thread; @@ -320,23 +354,64 @@ int main(int argc, char **argv) input->run(); inputs.push_back(input); } + + if (is_reexec) { + // Put back the existing clients. It doesn't matter which server we + // allocate them to, so just do round-robin. However, we need to add + // them after the mark pools have been set up. + for (int i = 0; i < loaded_state.clients_size(); ++i) { + servers->add_client_from_serialized(loaded_state.clients(i)); + } + } // All deserialized inputs should now have been taken care of, one way or the other. assert(deserialized_inputs.empty()); + // Start writing statistics. + pthread_t stats_thread; + StatsThreadParameters stats_parameters; // Must live for as long as the stats thread does. + if (!stats_file.empty()) { + stats_parameters.stats_file = stats_file; + stats_parameters.stats_interval = stats_interval; + pthread_create(&stats_thread, NULL, stats_thread_run, &stats_parameters); + } + signal(SIGHUP, hup); + + struct timeval server_start; + gettimeofday(&server_start, NULL); + if (is_reexec) { + // Measure time from we started deserializing (below) to now, when basically everything + // is up and running. This is, in other words, a conservative estimate of how long our + // “glitch” period was, not counting of course reconnects if the configuration changed. + double glitch_time = server_start.tv_sec - serialize_start.tv_sec + + 1e-6 * (server_start.tv_usec - serialize_start.tv_usec); + fprintf(stderr, "Re-exec happened in approx. %.0f ms.\n", glitch_time * 1000.0); + } while (!hupped) { usleep(100000); } // OK, we've been HUPed. Time to shut down everything, serialize, and re-exec. + gettimeofday(&serialize_start, NULL); + + if (!stats_file.empty()) { + pthread_kill(stats_thread, SIGHUP); + if (pthread_join(stats_thread, NULL) == -1) { + perror("pthread_join"); + exit(1); + } + } + pthread_kill(acceptor_thread, SIGHUP); if (pthread_join(acceptor_thread, NULL) == -1) { perror("pthread_join"); exit(1); } CubemapStateProto state; + state.set_serialize_start_sec(serialize_start.tv_sec); + state.set_serialize_start_usec(serialize_start.tv_usec); state.set_server_sock(server_sock); state.set_port(port);