@@ -89,14 +89,12 @@ def __init__(
89
89
self .parallel = parallel
90
90
self .cont_batching = cont_batching
91
91
92
- self ._logging_threads = []
93
- self ._status = "stopped"
94
- self .process = None
95
92
self ._check_resources ()
96
93
97
94
if logger is None :
98
95
logger = logging .getLogger (__name__ + ".Server" + str (self .port ))
99
- self .logger = logger
96
+ self ._logger = logger
97
+ self ._process = None
100
98
101
99
@classmethod
102
100
def from_huggingface (
@@ -135,9 +133,15 @@ def base_url(self) -> str:
135
133
return f"http://127.0.0.1:{ self .port } "
136
134
137
135
@property
138
- def status (self ) -> str :
139
- """The status of the server: 'stopped', 'starting', or 'running'."""
140
- return self ._status
136
+ def logger (self ) -> logging .Logger :
137
+ """The logger used for server output."""
138
+ return self ._logger
139
+
140
+ @logger .setter
141
+ def logger (self , logger : logging .Logger ):
142
+ self ._logger = logger
143
+ if self ._process is not None :
144
+ self ._process .logger = logger
141
145
142
146
def start (self ) -> None :
143
147
"""Start the server in a subprocess.
@@ -151,32 +155,31 @@ def start(self) -> None:
151
155
152
156
You can start and stop the server multiple times in a row.
153
157
"""
154
- if self .process is not None :
158
+ if self ._process is not None :
155
159
raise RuntimeError ("Server is already running." )
156
160
self ._check_resources ()
157
161
self .logger .info (
158
162
f"Starting server with command: '{ ' ' .join (self ._command )} '..."
159
163
)
160
- self .process = subprocess .Popen (
161
- self ._command , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True
162
- )
163
- self ._status = "starting"
164
- self ._logging_threads = self ._watch_outputs ()
164
+ self ._process = _RunningServerProcess (self ._command , self .logger )
165
165
166
166
def stop (self ) -> None :
167
167
"""Terminate the server subprocess. No-op if there is no active subprocess."""
168
- if self .process is None :
168
+ if self ._process is None :
169
169
return
170
- self .process .kill ()
171
- for thread in self ._logging_threads :
172
- thread .join ()
173
- self ._status = "stopped"
174
- self .process = None
170
+ self ._process .stop ()
171
+ self ._process = None
172
+
173
+ def wait_for_ready (self , * , timeout : int = 5 ) -> None :
174
+ """Wait until the server is ready to receive requests."""
175
+ if self ._process is None :
176
+ raise RuntimeError ("Server is not running." )
177
+ self ._process .wait_for_ready (timeout = timeout )
175
178
176
179
def __enter__ (self ):
177
180
"""Start the server when entering a context manager."""
178
181
self .start ()
179
- self .wait_for_ready ()
182
+ self ._process . wait_for_ready ()
180
183
return self
181
184
182
185
def __exit__ (self , exc_type , exc_val , exc_tb ):
@@ -200,22 +203,37 @@ def _check_resources(self) -> None:
200
203
if not self .model_path .exists ():
201
204
raise FileNotFoundError (f"Model weights not found at { self .model_path } ." )
202
205
206
+
207
+ class _RunningServerProcess :
208
+ def __init__ (self , args : list [str ], logger : logging .Logger ) -> None :
209
+ self .popen = subprocess .Popen (
210
+ args , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True
211
+ )
212
+ self .logger = logger
213
+ self ._logging_threads = self ._watch_outputs ()
214
+ self ._status = "starting"
215
+
203
216
def wait_for_ready (self , * , timeout : int = 5 ) -> None :
204
- """Wait until the server is ready to receive requests."""
205
217
if self ._status == "running" :
206
218
return
207
219
start = time .time ()
208
220
while time .time () - start < timeout :
209
- if self .process .poll () is not None :
210
- raise RuntimeError (
211
- f"Server exited unexpectedly with code { self .process .returncode } ."
212
- )
221
+ self ._check_not_exited ()
213
222
if self ._status == "running" :
214
223
self .logger .info ("Server started." )
215
224
return
216
225
time .sleep (0.1 )
217
226
raise TimeoutError (f"Server did not start within { timeout } seconds." )
218
227
228
+ def _check_not_exited (self ) -> None :
229
+ exit_code = self .popen .poll ()
230
+ if exit_code is None :
231
+ return
232
+ self .stop ()
233
+ raise RuntimeError (
234
+ f"Server exited unexpectedly with code { self .popen .returncode } ."
235
+ )
236
+
219
237
def _watch_outputs (self ) -> list [threading .Thread ]:
220
238
def watch (file : io .StringIO ):
221
239
for line in file :
@@ -224,8 +242,13 @@ def watch(file: io.StringIO):
224
242
self ._status = "running"
225
243
self .logger .info (line )
226
244
227
- std_out_thread = threading .Thread (target = watch , args = (self .process .stdout ,))
228
- std_err_thread = threading .Thread (target = watch , args = (self .process .stderr ,))
245
+ std_out_thread = threading .Thread (target = watch , args = (self .popen .stdout ,))
246
+ std_err_thread = threading .Thread (target = watch , args = (self .popen .stderr ,))
229
247
std_out_thread .start ()
230
248
std_err_thread .start ()
231
249
return [std_out_thread , std_err_thread ]
250
+
251
+ def stop (self ):
252
+ self .popen .kill ()
253
+ for thread in self ._logging_threads :
254
+ thread .join ()
0 commit comments