X-Git-Url: https://git.sesse.net/?p=cubemap;a=blobdiff_plain;f=main.cpp;h=db3be63f2d05a2cbcff27afa620205b97405f524;hp=2af1996fe670d7152c81552e00018f17052f3aac;hb=684496ba9c7def1421d045435b6b92e80bc54c74;hpb=656d6863b47e1791a310397d88e25fa897e05012 diff --git a/main.cpp b/main.cpp index 2af1996..db3be63 100644 --- a/main.cpp +++ b/main.cpp @@ -18,16 +18,16 @@ #include #include +#include "acceptor.h" +#include "markpool.h" #include "metacube.h" #include "parse.h" #include "server.h" #include "serverpool.h" #include "input.h" +#include "stats.h" #include "state.pb.h" -#define STREAM_ID "stream" -#define STREAM_URL "http://gruessi.zrh.sesse.net:4013/" - using namespace std; ServerPool *servers = NULL; @@ -38,160 +38,6 @@ 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) -{ - int server_sock = int(intptr_t(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; - } - - sockaddr_in6 addr; - socklen_t addrlen = sizeof(addr); - - // Get a new socket. - int sock = accept(server_sock, reinterpret_cast(&addr), &addrlen); - if (sock == -1 && errno == EINTR) { - continue; - } - if (sock == -1) { - perror("accept"); - usleep(100000); - continue; - } - - // Set the socket as nonblocking. - int one = 1; - if (ioctl(sock, FIONBIO, &one) == -1) { - perror("FIONBIO"); - exit(1); - } - - // Pick a server, round-robin, and hand over the socket to it. - servers->add_client(sock); - } - return NULL; -} - -struct StatsThreadParameters { - string stats_file; - int stats_interval; -}; - -void *stats_thread_run(void *arg) -{ - const StatsThreadParameters *parms = reinterpret_cast(arg); - while (!hupped) { - 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; - } - - fp = fdopen(fd, "w"); - if (fp == NULL) { - perror("fdopen"); - close(fd); - unlink(filename); - free(filename); - goto sleep; - } - - 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 (fclose(fp) == EOF) { - perror("fclose"); - unlink(filename); - free(filename); - goto sleep; - } - - if (rename(filename, parms->stats_file.c_str()) == -1) { - perror("rename"); - unlink(filename); - } - -sleep: - int left_to_sleep = parms->stats_interval; - do { - left_to_sleep = sleep(left_to_sleep); - } while (left_to_sleep > 0); - } - return NULL; -} - // Serialize the given state to a file descriptor, and return the (still open) // descriptor. int make_tempfile(const CubemapStateProto &state) @@ -222,6 +68,39 @@ int make_tempfile(const CubemapStateProto &state) return state_fd; } +CubemapStateProto collect_state(const timeval &serialize_start, + const vector acceptors, + const vector inputs, + ServerPool *servers, + int num_servers) +{ + CubemapStateProto state; + state.set_serialize_start_sec(serialize_start.tv_sec); + state.set_serialize_start_usec(serialize_start.tv_usec); + + for (size_t i = 0; i < acceptors.size(); ++i) { + state.add_acceptors()->MergeFrom(acceptors[i]->serialize()); + } + + for (size_t i = 0; i < inputs.size(); ++i) { + state.add_inputs()->MergeFrom(inputs[i]->serialize()); + } + + for (int i = 0; i < num_servers; ++i) { + CubemapStateProto local_state = servers->get_server(i)->serialize(); + + // The stream state should be identical between the servers, so we only store it once. + if (i == 0) { + state.mutable_streams()->MergeFrom(local_state.streams()); + } + for (int j = 0; j < local_state.clients_size(); ++j) { + state.add_clients()->MergeFrom(local_state.clients(j)); + } + } + + return state; +} + // Read the state back from the file descriptor made by make_tempfile, // and close it. CubemapStateProto read_tempfile(int state_fd) @@ -257,63 +136,151 @@ 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]; + } -int main(int argc, char **argv) + // 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; +} + +MarkPool *parse_mark_pool(map, MarkPool *> *mark_pools, const string &mark_str) { - fprintf(stderr, "\nCubemap starting.\n"); + 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()); + return NULL; + } - struct timeval serialize_start; - bool is_reexec = false; + 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()); - string config_filename = (argc == 1) ? "cubemap.config" : argv[1]; - vector config = parse_config(config_filename); + 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); + return NULL; + } - 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. + return get_mark_pool(mark_pools, from, to); +} - servers = new ServerPool(num_servers); +// Find all port statements in the configuration file, and create acceptors for htem. +vector create_acceptors( + const vector &config, + map *deserialized_acceptors) +{ + vector acceptors; + for (unsigned i = 0; i < config.size(); ++i) { + if (config[i].keyword != "port") { + continue; + } + if (config[i].arguments.size() != 1) { + fprintf(stderr, "ERROR: 'port' takes exactly one argument\n"); + exit(1); + } + int port = atoi(config[i].arguments[0].c_str()); + if (port < 1 || port >= 65536) { + fprintf(stderr, "WARNING: port %d is out of range (must be [1,65536>), ignoring\n", port); + continue; + } - 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; + Acceptor *acceptor = NULL; + map::iterator deserialized_acceptor_it = + deserialized_acceptors->find(port); + if (deserialized_acceptor_it != deserialized_acceptors->end()) { + acceptor = deserialized_acceptor_it->second; + deserialized_acceptors->erase(deserialized_acceptor_it); + } else { + int server_sock = create_server_socket(port); + acceptor = new Acceptor(server_sock, port); + } + acceptor->run(); + acceptors.push_back(acceptor); + } - fprintf(stderr, "Deserializing state from previous process... "); - int state_fd = atoi(argv[3]); - CubemapStateProto loaded_state = read_tempfile(state_fd); + // Close all acceptors that are no longer in the configuration file. + for (map::iterator acceptor_it = deserialized_acceptors->begin(); + acceptor_it != deserialized_acceptors->end(); + ++acceptor_it) { + acceptor_it->second->close_socket(); + delete acceptor_it->second; + } - serialize_start.tv_sec = loaded_state.serialize_start_sec(); - serialize_start.tv_usec = loaded_state.serialize_start_usec(); + return acceptors; +} - // Deserialize the streams. - for (int i = 0; i < loaded_state.streams_size(); ++i) { - servers->add_stream_from_serialized(loaded_state.streams(i)); - deserialized_stream_ids.insert(loaded_state.streams(i).stream_id()); +// Find all streams in the configuration file, and create inputs for them. +vector create_inputs(const vector &config, + map *deserialized_inputs) +{ + vector inputs; + for (unsigned i = 0; i < config.size(); ++i) { + if (config[i].keyword != "stream") { + continue; } + assert(config[i].arguments.size() == 1); + string stream_id = config[i].arguments[0]; - // 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)); + map::const_iterator src_it = + config[i].parameters.find("src"); + if (src_it == config[i].parameters.end()) { + fprintf(stderr, "WARNING: stream '%s' has no src= attribute, clients will not get any data.\n", + stream_id.c_str()); + continue; } - // 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( - loaded_state.inputs(i).stream_id(), - new Input(loaded_state.inputs(i)))); - } - - // Deserialize the server socket. - server_sock = loaded_state.server_sock(); - old_port = loaded_state.port(); - - fprintf(stderr, "done.\n"); + string src = src_it->second; + Input *input = NULL; + map::iterator deserialized_input_it = + deserialized_inputs->find(stream_id); + if (deserialized_input_it != deserialized_inputs->end()) { + input = deserialized_input_it->second; + if (input->get_url() != src) { + fprintf(stderr, "INFO: Stream '%s' has changed URL from '%s' to '%s', restarting input.\n", + stream_id.c_str(), input->get_url().c_str(), src.c_str()); + delete input; + input = NULL; + } + deserialized_inputs->erase(deserialized_input_it); + } + if (input == NULL) { + input = new Input(stream_id, src); + } + input->run(); + inputs.push_back(input); } + return inputs; +} - // Find all streams in the configuration file, and create them. +void create_streams(const vector &config, + const set &deserialized_stream_ids, + map *deserialized_inputs) +{ 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; @@ -327,6 +294,14 @@ int main(int argc, char **argv) servers->add_stream(stream_id); } expecting_stream_ids.erase(stream_id); + + // Set up marks, if so desired. + map::const_iterator mark_parm_it = + config[i].parameters.find("mark"); + if (mark_parm_it != config[i].parameters.end()) { + MarkPool *mark_pool = parse_mark_pool(&mark_pools, mark_parm_it->second); + servers->set_mark_pool(stream_id, mark_pool); + } } // Warn about any servers we've lost. @@ -338,78 +313,103 @@ int main(int argc, char **argv) fprintf(stderr, "WARNING: stream '%s' disappeared from the configuration file.\n", stream_id.c_str()); fprintf(stderr, " It will not be deleted, but clients will not get any new inputs.\n"); - if (deserialized_inputs.count(stream_id) != 0) { - delete deserialized_inputs[stream_id]; - deserialized_inputs.erase(stream_id); + if (deserialized_inputs->count(stream_id) != 0) { + delete (*deserialized_inputs)[stream_id]; + deserialized_inputs->erase(stream_id); } } +} - // Open a new server socket if we do not already have one, or if we changed ports. - if (server_sock != -1 && port != old_port) { - fprintf(stderr, "NOTE: Port changed from %d to %d; opening new socket.\n", old_port, port); - close(server_sock); - server_sock = -1; - } - if (server_sock == -1) { - server_sock = create_server_socket(port); - } +int main(int argc, char **argv) +{ + fprintf(stderr, "\nCubemap starting.\n"); - // 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"); - } + struct timeval serialize_start; + bool is_reexec = false; - servers->run(); + string config_filename = (argc == 1) ? "cubemap.config" : argv[1]; + vector config = parse_config(config_filename); - pthread_t acceptor_thread; - pthread_create(&acceptor_thread, NULL, acceptor_thread_run, reinterpret_cast(server_sock)); + int num_servers = fetch_config_int(config, "num_servers", 1, 20000, PARAMATER_MANDATORY); // Insanely high max limit. - // Find all streams in the configuration file, and create inputs for them. - vector inputs; - for (unsigned i = 0; i < config.size(); ++i) { - if (config[i].keyword != "stream") { - continue; - } - assert(config[i].arguments.size() == 1); - string stream_id = config[i].arguments[0]; + servers = new ServerPool(num_servers); - if (config[i].parameters.count("src") == 0) { - fprintf(stderr, "WARNING: stream '%s' has no src= attribute, clients will not get any data.\n", - stream_id.c_str()); - continue; + CubemapStateProto loaded_state; + set deserialized_stream_ids; + map deserialized_inputs; + map deserialized_acceptors; + if (argc == 4 && strcmp(argv[2], "-state") == 0) { + is_reexec = true; + + fprintf(stderr, "Deserializing state from previous process... "); + int state_fd = atoi(argv[3]); + 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) { + servers->add_stream_from_serialized(loaded_state.streams(i)); + deserialized_stream_ids.insert(loaded_state.streams(i).stream_id()); } - string src = config[i].parameters["src"]; - Input *input = NULL; - if (deserialized_inputs.count(stream_id) != 0) { - input = deserialized_inputs[stream_id]; - if (input->get_url() != src) { - fprintf(stderr, "INFO: Stream '%s' has changed URL from '%s' to '%s', restarting input.\n", - stream_id.c_str(), input->get_url().c_str(), src.c_str()); - delete input; - input = NULL; - } - deserialized_inputs.erase(stream_id); + // 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( + loaded_state.inputs(i).stream_id(), + new Input(loaded_state.inputs(i)))); + } + + // Convert the acceptor from older serialized formats. + if (loaded_state.has_server_sock() && loaded_state.has_port()) { + AcceptorProto *acceptor = loaded_state.add_acceptors(); + acceptor->set_server_sock(loaded_state.server_sock()); + acceptor->set_port(loaded_state.port()); } - if (input == NULL) { - input = new Input(stream_id, src); + + // Deserialize the acceptors. + for (int i = 0; i < loaded_state.acceptors_size(); ++i) { + deserialized_acceptors.insert(make_pair( + loaded_state.acceptors(i).port(), + new Acceptor(loaded_state.acceptors(i)))); } - input->run(); - inputs.push_back(input); + + fprintf(stderr, "done.\n"); } + // Find all streams in the configuration file, and create them. + create_streams(config, deserialized_stream_ids, &deserialized_inputs); + + // 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(); + + vector acceptors = create_acceptors(config, &deserialized_acceptors); + vector inputs = create_inputs(config, &deserialized_inputs); + // All deserialized inputs should now have been taken care of, one way or the other. assert(deserialized_inputs.empty()); + + 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)); + } + } // Start writing statistics. - pthread_t stats_thread; - StatsThreadParameters stats_parameters; // Must live for as long as the stats thread does. + StatsThread *stats_thread = NULL; 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); + stats_thread = new StatsThread(stats_file, stats_interval); + stats_thread->run(); } signal(SIGHUP, hup); @@ -432,46 +432,22 @@ int main(int argc, char **argv) // OK, we've been HUPed. Time to shut down everything, serialize, and re-exec. gettimeofday(&serialize_start, NULL); - if (!stats_file.empty()) { - if (pthread_join(stats_thread, NULL) == -1) { - perror("pthread_join"); - exit(1); - } + if (stats_thread != NULL) { + stats_thread->stop(); } - if (pthread_join(acceptor_thread, NULL) == -1) { - perror("pthread_join"); - exit(1); + for (size_t i = 0; i < acceptors.size(); ++i) { + acceptors[i]->stop(); } - - 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); - for (size_t i = 0; i < inputs.size(); ++i) { inputs[i]->stop(); - state.add_inputs()->MergeFrom(inputs[i]->serialize()); } - - for (int i = 0; i < num_servers; ++i) { - servers->get_server(i)->stop(); - - CubemapStateProto local_state = servers->get_server(i)->serialize(); - - // The stream state should be identical between the servers, so we only store it once. - if (i == 0) { - state.mutable_streams()->MergeFrom(local_state.streams()); - } - for (int j = 0; j < local_state.clients_size(); ++j) { - state.add_clients()->MergeFrom(local_state.clients(j)); - } - } - delete servers; + servers->stop(); fprintf(stderr, "Serializing state and re-execing...\n"); - int state_fd = make_tempfile(state); - + int state_fd = make_tempfile(collect_state( + serialize_start, acceptors, inputs, servers, num_servers)); + delete servers; + char buf[16]; sprintf(buf, "%d", state_fd);