65
65
from jormungandr .equipments import EquipmentProviderManager
66
66
from jormungandr .external_services import ExternalServiceManager
67
67
from jormungandr .utils import can_connect_to_database
68
- from jormungandr import pt_planners_manager , transient_socket
68
+ from jormungandr import pt_planners_manager
69
69
70
70
type_to_pttype = {
71
71
"stop_area" : request_pb2 .PlaceCodeRequest .StopArea , # type: ignore
@@ -101,8 +101,9 @@ def _getter(self):
101
101
return property (_getter )
102
102
103
103
104
- class Instance (transient_socket . TransientSocket ):
104
+ class Instance (object ):
105
105
name = None # type: Text
106
+ _sockets = None # type: Deque[Tuple[zmq.Socket, float]]
106
107
107
108
def __init__ (
108
109
self ,
@@ -121,15 +122,9 @@ def __init__(
121
122
ghost_words = None ,
122
123
instance_db = None ,
123
124
):
124
- super (Instance , self ).__init__ (
125
- name = name ,
126
- zmq_context = context ,
127
- zmq_socket = zmq_socket ,
128
- socket_ttl = app .config .get (str ('ZMQ_SOCKET_TTL_SECONDS' ), 10 ),
129
- )
130
-
131
125
self .geom = None
132
126
self .geojson = None
127
+ self ._sockets = deque ()
133
128
self .socket_path = zmq_socket
134
129
self ._scenario = None
135
130
self ._scenario_name = None
@@ -722,6 +717,66 @@ def places_proximity_radius(self):
722
717
instance_db = self .get_models ()
723
718
return get_value_or_default ('places_proximity_radius' , instance_db , self .name )
724
719
720
+ def reap_socket (self , ttl ):
721
+ # type: (int) -> None
722
+ if self .zmq_socket_type != 'transient' :
723
+ return
724
+ logger = logging .getLogger (__name__ )
725
+ now = time .time ()
726
+
727
+ def _reap_sockets (connector ):
728
+ while True :
729
+ try :
730
+ socket , t = connector ._sockets .popleft ()
731
+ if now - t > ttl :
732
+ logger .debug ("closing one socket for %s" , connector .name )
733
+ socket .setsockopt (zmq .LINGER , 0 )
734
+ socket .close ()
735
+ else :
736
+ connector ._sockets .appendleft ((socket , t ))
737
+ break # remaining socket are still in "keep alive" state
738
+ except IndexError :
739
+ break
740
+
741
+ for _ , planner in self ._pt_planner_manager .get_all_pt_planners ():
742
+ if planner .is_zmq_socket ():
743
+ _reap_sockets (planner )
744
+ _reap_sockets (self )
745
+
746
+ @contextmanager
747
+ def socket (self , context ):
748
+ sockets = self ._sockets
749
+ socket_path = self .socket_path
750
+
751
+ t = None
752
+ try :
753
+ socket , t = sockets .pop ()
754
+ except IndexError : # there is no socket available: lets create one
755
+ start = time .time ()
756
+ socket = context .socket (zmq .REQ )
757
+ socket .connect (socket_path )
758
+ logging .getLogger (__name__ ).info (
759
+ "it took %s ms to open a instance socket of %s during a request" ,
760
+ '%.2e' % ((time .time () - start ) * 1000 ),
761
+ self .name ,
762
+ )
763
+
764
+ try :
765
+ yield socket
766
+ finally :
767
+ if not socket .closed :
768
+ if t is not None and time .time () - t > app .config .get ("ZMQ_SOCKET_TTL_SECONDS" , 10 ):
769
+ start = time .time ()
770
+ socket .setsockopt (zmq .LINGER , 0 )
771
+ socket .close ()
772
+ logging .getLogger (__name__ ).info (
773
+ "it took %s ms to close a instance socket in %s" ,
774
+ '%.2e' % ((time .time () - start ) * 1000 ),
775
+ self .name ,
776
+ )
777
+ else :
778
+ sockets .append ((socket , t or time .time ()))
779
+
725
780
def send_and_receive (self , * args , ** kwargs ):
726
781
"""
727
782
encapsulate all call to kraken in a circuit breaker, this way we don't loose time calling dead instance
@@ -731,25 +786,37 @@ def send_and_receive(self, *args, **kwargs):
731
786
except pybreaker .CircuitBreakerError as e :
732
787
raise DeadSocketException (self .name , self .socket_path )
733
788
734
- def _send_and_receive (self , request , timeout = app .config .get ('INSTANCE_TIMEOUT' , 10 ), quiet = False , ** kwargs ):
735
- deadline = datetime .utcnow () + timedelta (milliseconds = timeout * 1000 )
789
+ def _send_and_receive (
790
+ self , request , timeout = app .config .get ('INSTANCE_TIMEOUT' , 10000 ), quiet = False , ** kwargs
791
+ ):
792
+ logger = logging .getLogger (__name__ )
793
+ deadline = datetime .utcnow () + timedelta (milliseconds = timeout )
736
794
request .deadline = deadline .strftime ('%Y%m%dT%H%M%S,%f' )
737
795
738
- if 'request_id' in kwargs and kwargs ['request_id' ]:
739
- request .request_id = kwargs ['request_id' ]
740
- else :
741
- try :
742
- request .request_id = flask .request .id
743
- except RuntimeError :
744
- # we aren't in a flask context, so there is no request
745
- if 'flask_request_id' in kwargs and kwargs ['flask_request_id' ]:
746
- request .request_id = kwargs ['flask_request_id' ]
747
-
748
- pb = self .call (request .SerializeToString (), timeout , debug_cb = lambda : six .text_type (request ))
749
- resp = response_pb2 .Response ()
750
- resp .ParseFromString (pb )
751
- self .update_property (resp )
752
- return resp
796
+ with self .socket (self .context ) as socket :
797
+ if 'request_id' in kwargs and kwargs ['request_id' ]:
798
+ request .request_id = kwargs ['request_id' ]
799
+ else :
800
+ try :
801
+ request .request_id = flask .request .id
802
+ except RuntimeError :
803
+ # we aren't in a flask context, so there is no request
804
+ if 'flask_request_id' in kwargs and kwargs ['flask_request_id' ]:
805
+ request .request_id = kwargs ['flask_request_id' ]
806
+
807
+ socket .send (request .SerializeToString ())
808
+ if socket .poll (timeout = timeout ) > 0 :
809
+ pb = socket .recv ()
810
+ resp = response_pb2 .Response ()
811
+ resp .ParseFromString (pb )
812
+ self .update_property (resp ) # we update the timezone and geom of the instances at each request
813
+ return resp
814
+ else :
815
+ socket .setsockopt (zmq .LINGER , 0 )
816
+ socket .close ()
817
+ if not quiet :
818
+ logger .error ('request on %s failed: %s' , self .socket_path , six .text_type (request ))
819
+ raise DeadSocketException (self .name , self .socket_path )
753
820
754
821
def get_id (self , id_ ):
755
822
"""
@@ -758,7 +825,7 @@ def get_id(self, id_):
758
825
req = request_pb2 .Request ()
759
826
req .requested_api = type_pb2 .place_uri
760
827
req .place_uri .uri = id_
761
- return self .send_and_receive (req , timeout = app .config .get (str ( 'INSTANCE_FAST_TIMEOUT' ), 1 ))
828
+ return self .send_and_receive (req , timeout = app .config .get ('INSTANCE_FAST_TIMEOUT' , 1000 ))
762
829
763
830
def has_id (self , id_ ):
764
831
"""
@@ -796,7 +863,7 @@ def get_external_codes(self, type_, id_):
796
863
req .place_code .type_code = "external_code"
797
864
req .place_code .code = id_
798
865
# we set the timeout to 1s
799
- return self .send_and_receive (req , timeout = app .config .get (str ( 'INSTANCE_FAST_TIMEOUT' ), 1 ))
866
+ return self .send_and_receive (req , timeout = app .config .get ('INSTANCE_FAST_TIMEOUT' , 1000 ))
800
867
801
868
def has_external_code (self , type_ , id_ ):
802
869
"""
@@ -852,7 +919,7 @@ def init(self):
852
919
request_id = "instance_init"
853
920
try :
854
921
# we use _send_and_receive to avoid the circuit breaker, we don't want fast fail on init :)
855
- resp = self ._send_and_receive (req , request_id = request_id , timeout = 1 , quiet = True )
922
+ resp = self ._send_and_receive (req , request_id = request_id , timeout = 1000 , quiet = True )
856
923
# the instance is automatically updated on a call
857
924
if self .publication_date != pub_date :
858
925
return True
0 commit comments