async def send_loop(self): while True: await asyncio.sleep(0.25) if len(self.send_queue) == 0: continue # Copy send queue and clear the global one queue = self.send_queue.copy() self.send_queue.clear() # Process and push out the queue. try: await self.instance.gbx.multicall(*queue) except Fault as e: if 'Login unknown' in str(e): return logger.exception(e) handle_exception(exception=e, module_name=__name__, func_name='send_loop') except Exception as e: logger.exception(e) handle_exception(exception=e, module_name=__name__, func_name='send_loop')
async def add_map(self, filename, insert=True, save_matchsettings=True): """ Add or insert map to current online playlist. :param filename: Load from filename relative to the 'Maps' directory on the dedicated host server. :param insert: Insert after the current map, this will make it play directly after the current map. True by default. :param save_matchsettings: Save match settings as well. :type filename: str :type insert: bool :type save_matchsettings: bool :raise: pyplanet.contrib.map.exceptions.MapIncompatible :raise: pyplanet.contrib.map.exceptions.MapException """ gbx_method = 'InsertMap' if insert else 'AddMap' try: result = await self._instance.gbx(gbx_method, filename) except Fault as e: if 'unknown' in e.faultString: raise MapNotFound('Map is not found on the server.') elif 'already' in e.faultString: raise MapException('Map already added to server.') raise MapException(e.faultString) # Try to save match settings. try: if save_matchsettings: await self.save_matchsettings() except Exception as e: handle_exception(e, __name__, 'add_map', extra_data={'EXTRAHOOK': 'Map Insert bug, see #306'}) return result
async def execute_receiver(receiver, args, kwargs, ignore_exceptions=False): try: if asyncio.iscoroutinefunction(receiver): if len(args) > 0: return receiver, await receiver(*args, **kwargs) return receiver, await receiver(**kwargs) if len(args) > 0: return receiver, receiver(*args, **kwargs) return receiver, receiver(**kwargs) except Exception as exc: if not ignore_exceptions: raise logger.exception(SignalException( 'Signal receiver \'{}\' => {} thrown an exception!'.format( receiver.__module__, receiver.__name__)), exc_info=False) # Handle, will send to sentry if it's related to the core/contrib apps. handle_exception(exc, receiver.__module__, receiver.__name__) # Log the actual exception. logger.exception(exc) return receiver, exc
async def listen(self): """ Listen to socket. """ try: while True: head = await self.reader.readexactly(8) size, handle = struct.unpack_from('<LL', head) body = await self.reader.readexactly(size) data = method = fault = None try: data, method = loads(body, use_builtin_types=True) except Fault as e: fault = e except ExpatError as e: # See #121 for this solution. handle_exception(exception=e, module_name=__name__, func_name='listen', extra_data={'body': body}) continue if data and len(data) == 1: data = data[0] self.event_loop.create_task(self.handle_payload(handle, method, data, fault)) except ConnectionResetError as e: logger.critical( 'Connection with the dedicated server has been closed, we will now close down the subprocess! {}'.format(str(e)) ) # When the connection has been reset, we will close the controller process so it can be restarted by the god # process. Exit code 10 gives the information to the god process. exit(10) except Exception as e: handle_exception(exception=e, module_name=__name__, func_name='listen') raise
async def execute(self, method, *args): payload = dumps(args, methodname=method, allow_none=True) body = gzip.compress(payload.encode('utf8')) try: res = await self.loop.run_in_executor(None, self.__request, body) data, _ = loads(res.text, use_datetime=True) if isinstance(data, (tuple, list)) and len(data) > 0 and len(data[0]) > 0: if isinstance(data[0][0], dict) and 'faultCode' in data[0][0]: raise DedimaniaFault(faultCode=data[0][0]['faultCode'], faultString=data[0][0]['faultString']) self.retries = 0 return data[0] raise DedimaniaTransportException('Invalid response from dedimania!') except (ConnectionError, ReadTimeout, ConnectionRefusedError, requests.exceptions.ConnectionError) as e: raise DedimaniaTransportException(e) from e except ConnectTimeout as e: raise DedimaniaTransportException(e) from e except DedimaniaTransportException: # Try to setup new session. self.retries += 1 if self.retries > 5: raise DedimaniaTransportException('Dedimania didn\'t gave the right answer after few retries!') self.client = requests.session() try: await self.authenticate() return await self.execute(method, *args) except Exception as e: logger.error('XML-RPC Fault retrieved from Dedimania: {}'.format(str(e))) handle_exception(e, __name__, 'execute') raise DedimaniaTransportException('Could not retrieve data from dedimania!') except DedimaniaFault as e: if 'Bad SessionId' in e.faultString or ('SessionId' in e.faultString and 'not found' in e.faultString): try: self.retries += 1 if self.retries > 5: raise DedimaniaTransportException('Max retries reached for reauthenticating with dedimania!') # Save original session ID. original_session_id = '{}'.format(self.session_id) # Reauthenticate await self.authenticate() # Replace session_id in args. if len(args) > 0 and len(args[0]) > 0 and isinstance(args[0][0], dict) and 'params' in args[0][0]: new_params = list(args[0][0]['params']) if (new_params and isinstance(new_params[0], str) and new_params[0] == original_session_id): new_params[0] = self.session_id args[0][0]['params'] = tuple(new_params) # Try again. return await self.execute(method, *args) except: return logger.error('XML-RPC Fault retrieved from Dedimania: {}'.format(str(e))) handle_exception(e, __name__, 'execute', extra_data={ 'dedimania_retries': self.retries, }) raise DedimaniaTransportException('Could not retrieve data from dedimania!')
async def execute(self, method, *args): payload = dumps(args, methodname=method, allow_none=True) body = gzip.compress(payload.encode('utf8')) try: res = await self.loop.run_in_executor(None, self.__request, body) data, _ = loads(res.text, use_datetime=True) if isinstance( data, (tuple, list)) and len(data) > 0 and len(data[0]) > 0: if isinstance(data[0][0], dict) and 'faultCode' in data[0][0]: raise DedimaniaFault(faultCode=data[0][0]['faultCode'], faultString=data[0][0]['faultString']) self.retries = 0 return data[0] raise DedimaniaTransportException( 'Invalid response from dedimania!') except (ConnectionError, ReadTimeout) as e: raise DedimaniaTransportException(e) from e except ConnectTimeout as e: raise DedimaniaTransportException(e) from e except DedimaniaTransportException: # Try to setup new session. self.retries += 1 if self.retries > 5: raise DedimaniaTransportException( 'Dedimania didn\'t gave the right answer after few retries!' ) self.client = requests.session() try: await self.authenticate() return await self.execute(method, *args) except Exception as e: logger.error( 'XML-RPC Fault retrieved from Dedimania: {}'.format( str(e))) handle_exception(e, __name__, 'execute') raise DedimaniaTransportException( 'Could not retrieve data from dedimania!') except DedimaniaFault as e: if 'Bad SessionId' in e.faultString: try: self.retries += 1 if self.retries > 5: raise DedimaniaTransportException( 'Max retries reached for reauthenticating with dedimania!' ) await self.authenticate() return await self.execute(method, *args) except: return logger.error('XML-RPC Fault retrieved from Dedimania: {}'.format( str(e))) handle_exception(e, __name__, 'execute') raise DedimaniaTransportException( 'Could not retrieve data from dedimania!')
async def refresh_records(self): try: player_list, self.current_script = await asyncio.gather( self.instance.gbx('GetPlayerList', -1, 0), self.instance.mode_manager.get_current_script(), ) self.server_max_rank, modes, player_infos, self.current_records = await self.api.get_map_details( self.instance.map_manager.current_map, self.current_script, server_name=self.instance.game.server_name, server_comment='', is_private=self.instance.game.server_is_private, max_players=self.instance.game. server_max_players['CurrentValue'], max_specs=self.instance.game.server_max_specs['CurrentValue'], players=player_list, server_login=self.instance.game.server_player_login) self.ready = True except DedimaniaTransportException as e: self.ready = False if 'Max retries exceeded' in str(e): message = '$f00Error: Dedimania seems down?' else: message = '$f00Error: Dedimania error occured!' logger.exception(e) await self.instance.chat(message) return except DedimaniaFault as e: self.ready = False if 'session' in str(e).lower(): handle_exception(e, module_name=__name__, func_name='refresh_records') logger.error('Dedimania gave an Fault: {}'.format(str(e))) self.current_records = list() return except Exception as e: self.ready = False handle_exception(e, module_name=__name__, func_name='refresh_records') logger.exception(e) self.current_records = list() return for info in player_infos: self.player_info[info['Login']] = dict( banned=False, login=info['Login'], max_rank=info['MaxRank'], )
def check_memory_leak(self): asyncio.get_event_loop().call_later(30, self.check_memory_leak) logger.debug('Checking for memory leaks...') if 0 < len(gc.garbage) < 20: logger.warning('Found possible memory leaks: {}'.format( gc.garbage)) elif len(gc.garbage) >= 20: logger.error('Found memory leaks: {}'.format(gc.garbage)) if not self.reported: try: raise MemoryError('Found memory leaks: {}'.format( gc.garbage)) except MemoryError as e: log.handle_exception(exception=e, extra_data=dict(leaks=gc.garbage)) self.reported = True
async def refresh_records(self): try: player_list, self.current_script = await asyncio.gather( self.instance.gbx('GetPlayerList', -1, 0), self.instance.mode_manager.get_current_script(), ) self.server_max_rank, modes, player_infos, self.current_records = await self.api.get_map_details( self.instance.map_manager.current_map, self.current_script, server_name=self.instance.game.server_name, server_comment='', is_private=self.instance.game.server_is_private, max_players=self.instance.game.server_max_players, max_specs=self.instance.game.server_max_specs, players=player_list, server_login=self.instance.game.server_player_login ) self.ready = True except DedimaniaNotSupportedException as e: self.ready = False await self.instance.chat('$0b3Dedimania doesn\'t support or know the current script mode {}'.format( self.current_script )) logger.warning('Dedimania doesn\'t support or known the mode {}'.format(self.current_script)) # Still silently report. handle_exception(e, module_name=__name__, func_name='refresh_records', extra_data={ 'script': self.current_script }) return except DedimaniaTransportException as e: self.ready = False if 'Max retries exceeded' in str(e) or 'Max retries reached' in str(e): message = '$f00Error: Dedimania seems down? We retried to connect but after several retries it kept failing.' else: message = '$f00Error: Dedimania error occured! ({})'.format(str(e)) logger.exception(e) await self.instance.chat(message) return except DedimaniaFault as e: self.ready = False if 'session' in str(e).lower(): handle_exception(e, module_name=__name__, func_name='refresh_records') logger.error('Dedimania gave an Fault: {}'.format(str(e))) self.current_records = list() return except Exception as e: self.ready = False handle_exception(e, module_name=__name__, func_name='refresh_records') logger.exception(e) self.current_records = list() return for info in player_infos: self.player_info[info['Login']] = dict( banned=False, login=info['Login'], max_rank=info['MaxRank'], )
async def player_finish(self, player, race_time, lap_time, cps, flow, raw, **kwargs): record_limit = await self.setting_record_limit.get_value() chat_announce = await self.setting_chat_announce.get_value() async with self.lock: current_records = [x for x in self.current_records if x.player.login == player.login] score = lap_time previous_index = None previous_time = None if len(current_records) > 0: current_record = current_records[0] if score > current_record.score: # No improvement, ignore return # Temporary make index + time local for the messages. previous_index = self.current_records.index(current_record) + 1 previous_time = current_record.score # If equal, only show message. if score == current_record.score and (record_limit == 0 or previous_index <= record_limit): message = '$fff{}$z$s$0f3 equalled the $fff{}.$0f3 Local Record: $fff\uf017 {}$0f3.'.format( player.nickname, previous_index, times.format_time(score) ) if chat_announce >= previous_index: return await self.instance.chat(message) elif chat_announce != 0: return await self.instance.chat(message, player.login) else: current_record = LocalRecord( map=self.instance.map_manager.current_map, player=player, ) # Set details (score + cps times). current_record.score = score current_record.checkpoints = ','.join([str(cp) for cp in cps]) # Add to list when it's a new record! if current_record.get_id() is None: self.current_records.append(current_record) # (Re)sort the record list. self.current_records.sort(key=lambda x: x.score) new_index = self.current_records.index(current_record) + 1 if new_index == 1: map = next((m for m in self.instance.map_manager.maps if m.uid == self.instance.map_manager.current_map.uid), None) if map is not None: map.local = {'record_count': len(self.current_records), 'first_record': current_record} # Prepare messages. if previous_index is not None and (record_limit == 0 or previous_index <= record_limit): if new_index < previous_index: message = '$fff{}$z$s$0f3 gained the $fff{}.$0f3 Local Record: $fff\uf017 {}$0f3 ($fff{}.$0f3 $fff-{}$0f3).'.format( player.nickname, new_index, times.format_time(score), previous_index, times.format_time((previous_time - score)) ) else: message = '$fff{}$z$s$0f3 improved the $fff{}.$0f3 Local Record: $fff\uf017 {}$0f3 ($fff-{}$0f3).'.format( player.nickname, new_index, times.format_time(score), times.format_time((previous_time - score)) ) else: message = '$fff{}$z$s$0f3 drove the $fff{}.$0f3 Local Record: $fff\uf017 {}$0f3.'.format( player.nickname, new_index, times.format_time(score) ) # Save to database (but don't wait for it). try: asyncio.ensure_future(current_record.save()) except Exception as e: # To investigate #283. handle_exception(e, __name__, 'player_finish', extra_data={ 'own_records': current_records, 'own_record': current_record }) if self.widget is None: self.widget = LocalRecordsWidget(self) coros = [self.widget.display()] if record_limit == 0 or new_index <= record_limit: if chat_announce >= new_index: coros.append(self.instance.chat(message)) elif chat_announce != 0: coros.append(self.instance.chat(message, player)) await asyncio.gather(*coros) # Reload map referenced information asyncio.ensure_future(self.load_map_locals(map=self.instance.map_manager.current_map))