1414#include < string>
1515#include < thread>
1616#include < vector>
17+ #include < memory>
18+ #include < csignal>
19+ #include < atomic>
20+ #include < functional>
21+ #include < cstdlib>
22+ #if defined (_WIN32)
23+ #include < windows.h>
24+ #endif
1725
1826using namespace httplib ;
1927using json = nlohmann::ordered_json;
2028
29+ enum server_state {
30+ SERVER_STATE_LOADING_MODEL, // Server is starting up, model not fully loaded yet
31+ SERVER_STATE_READY, // Server is ready and model is loaded
32+ };
33+
2134namespace {
2235
2336// output formats
@@ -27,6 +40,20 @@ const std::string srt_format = "srt";
2740const std::string vjson_format = " verbose_json" ;
2841const std::string vtt_format = " vtt" ;
2942
43+ std::function<void (int )> shutdown_handler;
44+ std::atomic_flag is_terminating = ATOMIC_FLAG_INIT;
45+
46+ inline void signal_handler (int signal) {
47+ if (is_terminating.test_and_set ()) {
48+ // in case it hangs, we can force terminate the server by hitting Ctrl+C twice
49+ // this is for better developer experience, we can remove when the server is stable enough
50+ fprintf (stderr, " Received second interrupt, terminating immediately.\n " );
51+ exit (1 );
52+ }
53+
54+ shutdown_handler (signal);
55+ }
56+
3057struct server_params
3158{
3259 std::string hostname = " 127.0.0.1" ;
@@ -654,6 +681,9 @@ int main(int argc, char ** argv) {
654681 }
655682 }
656683
684+ std::unique_ptr<httplib::Server> svr = std::make_unique<httplib::Server>();
685+ std::atomic<server_state> state{SERVER_STATE_LOADING_MODEL};
686+
657687 struct whisper_context * ctx = whisper_init_from_file_with_params (params.model .c_str (), cparams);
658688
659689 if (ctx == nullptr ) {
@@ -663,9 +693,10 @@ int main(int argc, char ** argv) {
663693
664694 // initialize openvino encoder. this has no effect on whisper.cpp builds that don't have OpenVINO configured
665695 whisper_ctx_init_openvino_encoder (ctx, nullptr , params.openvino_encode_device .c_str (), nullptr );
696+ state.store (SERVER_STATE_READY);
697+
666698
667- Server svr;
668- svr.set_default_headers ({{" Server" , " whisper.cpp" },
699+ svr->set_default_headers ({{" Server" , " whisper.cpp" },
669700 {" Access-Control-Allow-Origin" , " *" },
670701 {" Access-Control-Allow-Headers" , " content-type, authorization" }});
671702
@@ -744,15 +775,15 @@ int main(int argc, char ** argv) {
744775 whisper_params default_params = params;
745776
746777 // this is only called if no index.html is found in the public --path
747- svr. Get (sparams.request_path + " /" , [&default_content ](const Request &, Response &res){
778+ svr-> Get (sparams.request_path + " /" , [&](const Request &, Response &res){
748779 res.set_content (default_content, " text/html" );
749780 return false ;
750781 });
751782
752- svr. Options (sparams.request_path + sparams.inference_path , [&](const Request &, Response &){
783+ svr-> Options (sparams.request_path + sparams.inference_path , [&](const Request &, Response &){
753784 });
754785
755- svr. Post (sparams.request_path + sparams.inference_path , [&](const Request &req, Response &res){
786+ svr-> Post (sparams.request_path + sparams.inference_path , [&](const Request &req, Response &res){
756787 // acquire whisper model mutex lock
757788 std::lock_guard<std::mutex> lock (whisper_mutex);
758789
@@ -1068,8 +1099,9 @@ int main(int argc, char ** argv) {
10681099 // reset params to their defaults
10691100 params = default_params;
10701101 });
1071- svr. Post (sparams.request_path + " /load" , [&](const Request &req, Response &res){
1102+ svr-> Post (sparams.request_path + " /load" , [&](const Request &req, Response &res){
10721103 std::lock_guard<std::mutex> lock (whisper_mutex);
1104+ state.store (SERVER_STATE_LOADING_MODEL);
10731105 if (!req.has_file (" model" ))
10741106 {
10751107 fprintf (stderr, " error: no 'model' field in the request\n " );
@@ -1101,18 +1133,25 @@ int main(int argc, char ** argv) {
11011133 // initialize openvino encoder. this has no effect on whisper.cpp builds that don't have OpenVINO configured
11021134 whisper_ctx_init_openvino_encoder (ctx, nullptr , params.openvino_encode_device .c_str (), nullptr );
11031135
1136+ state.store (SERVER_STATE_READY);
11041137 const std::string success = " Load was successful!" ;
11051138 res.set_content (success, " application/text" );
11061139
11071140 // check if the model is in the file system
11081141 });
11091142
1110- svr.Get (sparams.request_path + " /health" , [&](const Request &, Response &res){
1111- const std::string health_response = " {\" status\" :\" ok\" }" ;
1112- res.set_content (health_response, " application/json" );
1143+ svr->Get (sparams.request_path + " /health" , [&](const Request &, Response &res){
1144+ server_state current_state = state.load ();
1145+ if (current_state == SERVER_STATE_READY) {
1146+ const std::string health_response = " {\" status\" :\" ok\" }" ;
1147+ res.set_content (health_response, " application/json" );
1148+ } else {
1149+ res.set_content (" {\" status\" :\" loading model\" }" , " application/json" );
1150+ res.status = 503 ;
1151+ }
11131152 });
11141153
1115- svr. set_exception_handler ([](const Request &, Response &res, std::exception_ptr ep) {
1154+ svr-> set_exception_handler ([](const Request &, Response &res, std::exception_ptr ep) {
11161155 const char fmt[] = " 500 Internal Server Error\n %s" ;
11171156 char buf[BUFSIZ];
11181157 try {
@@ -1126,7 +1165,7 @@ int main(int argc, char ** argv) {
11261165 res.status = 500 ;
11271166 });
11281167
1129- svr. set_error_handler ([](const Request &req, Response &res) {
1168+ svr-> set_error_handler ([](const Request &req, Response &res) {
11301169 if (res.status == 400 ) {
11311170 res.set_content (" Invalid request" , " text/plain" );
11321171 } else if (res.status != 500 ) {
@@ -1136,29 +1175,61 @@ int main(int argc, char ** argv) {
11361175 });
11371176
11381177 // set timeouts and change hostname and port
1139- svr. set_read_timeout (sparams.read_timeout );
1140- svr. set_write_timeout (sparams.write_timeout );
1178+ svr-> set_read_timeout (sparams.read_timeout );
1179+ svr-> set_write_timeout (sparams.write_timeout );
11411180
1142- if (!svr. bind_to_port (sparams.hostname , sparams.port ))
1181+ if (!svr-> bind_to_port (sparams.hostname , sparams.port ))
11431182 {
11441183 fprintf (stderr, " \n couldn't bind to server socket: hostname=%s port=%d\n\n " ,
11451184 sparams.hostname .c_str (), sparams.port );
11461185 return 1 ;
11471186 }
11481187
11491188 // Set the base directory for serving static files
1150- svr. set_base_dir (sparams.public_path );
1189+ svr-> set_base_dir (sparams.public_path );
11511190
11521191 // to make it ctrl+clickable:
11531192 printf (" \n whisper server listening at http://%s:%d\n\n " , sparams.hostname .c_str (), sparams.port );
11541193
1155- if (!svr.listen_after_bind ())
1156- {
1157- return 1 ;
1158- }
1194+ shutdown_handler = [&](int signal) {
1195+ printf (" \n Caught signal %d, shutting down gracefully...\n " , signal);
1196+ if (svr) {
1197+ svr->stop ();
1198+ }
1199+ };
1200+
1201+ #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
1202+ struct sigaction sigint_action;
1203+ sigint_action.sa_handler = signal_handler;
1204+ sigemptyset (&sigint_action.sa_mask );
1205+ sigint_action.sa_flags = 0 ;
1206+ sigaction (SIGINT, &sigint_action, NULL );
1207+ sigaction (SIGTERM, &sigint_action, NULL );
1208+ #elif defined (_WIN32)
1209+ auto console_ctrl_handler = +[](DWORD ctrl_type) -> BOOL {
1210+ return (ctrl_type == CTRL_C_EVENT) ? (signal_handler (SIGINT), true ) : false ;
1211+ };
1212+ SetConsoleCtrlHandler (reinterpret_cast <PHANDLER_ROUTINE>(console_ctrl_handler), true );
1213+ #endif
1214+
1215+ // clean up function, to be called before exit
1216+ auto clean_up = [&]() {
1217+ whisper_print_timings (ctx);
1218+ whisper_free (ctx);
1219+ };
1220+
1221+ std::thread t ([&] {
1222+ if (!svr->listen_after_bind ()) {
1223+ fprintf (stderr, " error: server listen failed\n " );
1224+ }
1225+ });
1226+
1227+ svr->wait_until_ready ();
1228+
1229+ t.join ();
1230+
11591231
1160- whisper_print_timings (ctx);
1161- whisper_free (ctx);
1232+ clean_up ();
11621233
11631234 return 0 ;
11641235}
0 commit comments