customserver: fix memory leak (#3864)
This commit is contained in:
		
							parent
							
								
									34a3b5f058
								
							
						
					
					
						commit
						1a41e1acc8
					
				| 
						 | 
					@ -67,6 +67,21 @@ def update_dict(dictionary, entries):
 | 
				
			||||||
    return dictionary
 | 
					    return dictionary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def queue_gc():
 | 
				
			||||||
 | 
					    import gc
 | 
				
			||||||
 | 
					    from threading import Thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gc_thread: typing.Optional[Thread] = getattr(queue_gc, "_thread", None)
 | 
				
			||||||
 | 
					    def async_collect():
 | 
				
			||||||
 | 
					        time.sleep(2)
 | 
				
			||||||
 | 
					        setattr(queue_gc, "_thread", None)
 | 
				
			||||||
 | 
					        gc.collect()
 | 
				
			||||||
 | 
					    if not gc_thread:
 | 
				
			||||||
 | 
					        gc_thread = Thread(target=async_collect)
 | 
				
			||||||
 | 
					        setattr(queue_gc, "_thread", gc_thread)
 | 
				
			||||||
 | 
					        gc_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# functions callable on storable data on the server by clients
 | 
					# functions callable on storable data on the server by clients
 | 
				
			||||||
modify_functions = {
 | 
					modify_functions = {
 | 
				
			||||||
    # generic:
 | 
					    # generic:
 | 
				
			||||||
| 
						 | 
					@ -551,6 +566,9 @@ class Context:
 | 
				
			||||||
                        self.logger.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.")
 | 
					                        self.logger.info(f"Saving failed. Retry in {self.auto_save_interval} seconds.")
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        self.save_dirty = False
 | 
					                        self.save_dirty = False
 | 
				
			||||||
 | 
					                if not atexit_save:  # if atexit is used, that keeps a reference anyway
 | 
				
			||||||
 | 
					                    queue_gc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.auto_saver_thread = threading.Thread(target=save_regularly, daemon=True)
 | 
					            self.auto_saver_thread = threading.Thread(target=save_regularly, daemon=True)
 | 
				
			||||||
            self.auto_saver_thread.start()
 | 
					            self.auto_saver_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,6 +72,14 @@ class WebHostContext(Context):
 | 
				
			||||||
        self.video = {}
 | 
					        self.video = {}
 | 
				
			||||||
        self.tags = ["AP", "WebHost"]
 | 
					        self.tags = ["AP", "WebHost"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __del__(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            import psutil
 | 
				
			||||||
 | 
					            from Utils import format_SI_prefix
 | 
				
			||||||
 | 
					            self.logger.debug(f"Context destroyed, Mem: {format_SI_prefix(psutil.Process().memory_info().rss, 1024)}iB")
 | 
				
			||||||
 | 
					        except ImportError:
 | 
				
			||||||
 | 
					            self.logger.debug("Context destroyed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _load_game_data(self):
 | 
					    def _load_game_data(self):
 | 
				
			||||||
        for key, value in self.static_server_data.items():
 | 
					        for key, value in self.static_server_data.items():
 | 
				
			||||||
            # NOTE: attributes are mutable and shared, so they will have to be copied before being modified
 | 
					            # NOTE: attributes are mutable and shared, so they will have to be copied before being modified
 | 
				
			||||||
| 
						 | 
					@ -249,6 +257,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
 | 
				
			||||||
                ctx = WebHostContext(static_server_data, logger)
 | 
					                ctx = WebHostContext(static_server_data, logger)
 | 
				
			||||||
                ctx.load(room_id)
 | 
					                ctx.load(room_id)
 | 
				
			||||||
                ctx.init_save()
 | 
					                ctx.init_save()
 | 
				
			||||||
 | 
					                assert ctx.server is None
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    ctx.server = websockets.serve(
 | 
					                    ctx.server = websockets.serve(
 | 
				
			||||||
                        functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
 | 
					                        functools.partial(server, ctx=ctx), ctx.host, ctx.port, ssl=ssl_context)
 | 
				
			||||||
| 
						 | 
					@ -279,6 +288,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
 | 
				
			||||||
                    ctx.auto_shutdown = Room.get(id=room_id).timeout
 | 
					                    ctx.auto_shutdown = Room.get(id=room_id).timeout
 | 
				
			||||||
                if ctx.saving:
 | 
					                if ctx.saving:
 | 
				
			||||||
                    setattr(asyncio.current_task(), "save", lambda: ctx._save(True))
 | 
					                    setattr(asyncio.current_task(), "save", lambda: ctx._save(True))
 | 
				
			||||||
 | 
					                assert ctx.shutdown_task is None
 | 
				
			||||||
                ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
 | 
					                ctx.shutdown_task = asyncio.create_task(auto_shutdown(ctx, []))
 | 
				
			||||||
                await ctx.shutdown_task
 | 
					                await ctx.shutdown_task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -325,7 +335,7 @@ def run_server_process(name: str, ponyconfig: dict, static_server_data: dict,
 | 
				
			||||||
        def run(self):
 | 
					        def run(self):
 | 
				
			||||||
            while 1:
 | 
					            while 1:
 | 
				
			||||||
                next_room = rooms_to_run.get(block=True,  timeout=None)
 | 
					                next_room = rooms_to_run.get(block=True,  timeout=None)
 | 
				
			||||||
                gc.collect(0)
 | 
					                gc.collect()
 | 
				
			||||||
                task = asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
 | 
					                task = asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
 | 
				
			||||||
                self._tasks.append(task)
 | 
					                self._tasks.append(task)
 | 
				
			||||||
                task.add_done_callback(self._done)
 | 
					                task.add_done_callback(self._done)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue