UN-3310 Faster server startup.#290
Conversation
| agent_network_provider, | ||
| agent_server_logging) | ||
| self.allowed_agents[agent_name] = agent_service_provider | ||
|
|
There was a problem hiding this comment.
Instead of AsyncAgentService instances, "allowed_agents" map now contains AsyncAgentServiceProvider instances for lazy instantiation of AsyncAgentService.
| self.server_logging: AgentServerLogging = server_logging | ||
| self.agent_network_provider: AgentNetworkProvider = agent_network_provider | ||
| self.agent_name: str = agent_name | ||
| self.lock: Lock = Lock() |
There was a problem hiding this comment.
Save constructor parameters for later - when we will need to instantiate AgentService object on the first use.
| self.security_cfg, | ||
| self.agent_name, | ||
| self.agent_network_provider, | ||
| self.server_logging) |
There was a problem hiding this comment.
Standard instance creation pattern for multi-threaded environment.
| from typing import Dict | ||
| from threading import Lock | ||
| import copy | ||
|
|
There was a problem hiding this comment.
This class more or less duplicates AgentServiceProvider logic, but for AsyncAgentService instances.
Maybe we could cook up something more generic and elegant Pythonic style for both classes,
but not in this PR.
| # Service is not yet instantiated - it has no requests | ||
| return 0 | ||
| service: AgentService = self.service_provider.get_service() | ||
| return service.get_request_count() |
There was a problem hiding this comment.
That's perf optimization: if service instance has not been instantiated, then its request count is obviously 0.
Otherwise with current gRPC server logic, we'll always instantiate all agent services,
even when not using gRPC service at all.
| agent_name, | ||
| agent_network_provider, | ||
| server_logging) | ||
|
|
There was a problem hiding this comment.
Create "lazy" service provider - instantiate actual AgentService only when needed.
| service: AgentService = self.service_provider.get_service() | ||
| response_dict: Dict[str, Any] =\ | ||
| service.function(request_dict, request_metadata, context) | ||
|
|
There was a problem hiding this comment.
Here and below - get AgentService from AgentServiceProvider for actual request processing.
| self.do_finish() | ||
| return None | ||
| return service_provider.get_service() | ||
|
|
There was a problem hiding this comment.
Factor out common logic for getting AsyncAgentService for request processing.
Will be used in all (3) neuro-san API requests.
|
|
||
| self.logger.info(self.get_metadata(), f"[REQUEST RECEIVED] {self.request.method} {self.request.uri}") | ||
| self.logger.debug(self.get_metadata(), f"[REQUEST RECEIVED] {self.request.method} {self.request.uri}") | ||
|
|
There was a problem hiding this comment.
Less chatty.
|
|
||
| service: AsyncAgentService = self.agent_policy.allow(agent_name) | ||
| service: AsyncAgentService = await self.get_service(agent_name, metadata) | ||
| if service is None: |
There was a problem hiding this comment.
Here and below - use common function from base_request_handler module.
There was a problem hiding this comment.
Nice that this consolidates all that other status/error/do_finish business.
| self.set_status(404) | ||
| self.logger.error({}, "error: Invalid request path %s", self.request.path) | ||
| self.do_finish() | ||
| return |
There was a problem hiding this comment.
Use common function from base_request_handler module.
| self.logger.error({}, "error: Invalid request path %s", self.request.path) | ||
| self.do_finish() | ||
| return | ||
|
|
There was a problem hiding this comment.
Use common function from base_request_handler module.
| app.listen(self.http_port) | ||
| self.logger.info({}, "HTTP server is running on port %d", self.http_port) | ||
| self.logger.info({}, "HTTP server is shutting down after %d requests", self.requests_limit) | ||
|
|
There was a problem hiding this comment.
Start actually listening after all preparations are done.
Log server start at this point.
| self.server_logging) | ||
| return self.service_instance | ||
|
|
||
| def service_created(self) -> bool: |
There was a problem hiding this comment.
Nit: Since this is a boolean, perhaps a better name would be is_service_created()
Here and below in the async version.
| self.security_cfg, | ||
| self.agent_name, | ||
| self.agent_network_provider, | ||
| self.server_logging) |
There was a problem hiding this comment.
For next PR consideration: Since all of this is the same except for the type of service_instance, maybe there is a path to abstraction here.
However well functioning all the async stuff is, someday Python will clean up this copy-paste cancer on the language.
|
|
||
| service: AsyncAgentService = self.agent_policy.allow(agent_name) | ||
| service: AsyncAgentService = await self.get_service(agent_name, metadata) | ||
| if service is None: |
There was a problem hiding this comment.
Nice that this consolidates all that other status/error/do_finish business.
Main idea of this PR is switch to "lazy" service instances instantiation.
Right now, we always create service instances - 2 of them in fact for each agent defined in server manifest;
one for gRPC, one for http, even if say we don't use gRPC for this particular server invocation,
and only use a single agent for chat.
Each service creation involves reading at least 2 files, which adds up.
Changing to service instantiation on demand makes server start up much faster.
There is an initial lag of course for loading all Python classes, but nothing to be done for that I guess.