Skip to content

Commit 41bea97

Browse files
committed
feat: update README files with task completion status and enhance process management in main.py
1 parent bf07d2d commit 41bea97

File tree

4 files changed

+103
-50
lines changed

4 files changed

+103
-50
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ if __name__ == "__main__":
352352

353353
- [ ] **Custom Functions**: Manage Python functions by creating separate threads.
354354
- [ ] **Per-Project Conditions**: Allow a specific project to pause only if a specific app opens.
355-
- [ ] **Graceful Shutdown**: Try a graceful shutdown (SIGINT/CTRL+C) before forcing process termination.
356-
- [ ] **Dead Process Handling**: Periodically check if started processes are still alive.
355+
- [x] **Graceful Shutdown**: Try a graceful shutdown (SIGINT/CTRL+C) before forcing process termination.
356+
- [x] **Dead Process Handling**: Periodically check if started processes are still alive.
357357
- [ ] **Project Abstraction**: Refactor into classes (`PythonProject`, `NodeProject`) to easily add new languages.
358358
- [x] **Type Hinting**: Improve typing across all methods for better IDE support.
359359

@@ -380,7 +380,10 @@ if __name__ == "__main__":
380380
- [x] Configuration via code arguments
381381
- [x] Event callbacks (`on_pause` and `on_resume`)
382382
- [x] Configurable log levels (DEBUG, INFO, WARNING, ERROR)
383-
- [x] Safe process termination (tree-kill)
383+
- [x] Safe process termination (Graceful Shutdown + Kill)
384+
- [x] Process health monitoring (Automatic restart/idle on exit)
385+
- [x] Option to enable/disable script windows (Windows OS only)
386+
- [x] Type Hinting: Improved typing across all methods for better IDE support.
384387

385388
---
386389

README_ptBR.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,8 @@ if __name__ == "__main__":
385385

386386
- [ ] **Funções Customizadas**: Gerenciar funções Python criando threads separadas.
387387
- [ ] **Condições por Projeto**: Permitir que um projeto específico só pause se um aplicativo específico abrir.
388-
- [ ] **Encerramento Amigável**: Tentar um encerramento gracioso (SIGINT/CTRL+C) antes de forçar o término do processo.
389-
- [ ] **Tratamento de Processos Mortos**: Verificar periodicamente se os processos iniciados ainda estão vivos.
388+
- [x] **Encerramento Amigável**: Tentar um encerramento gracioso (SIGINT/CTRL+C) antes de forçar o término do processo.
389+
- [x] **Tratamento de Processos Mortos**: Verificar periodicamente se os processos iniciados ainda estão vivos.
390390
- [ ] **Abstração de Projetos**: Refatorar para classes (`PythonProject`, `NodeProject`) facilitando a adição de novas linguagens.
391391
- [ ] Arrumar bugs relacionado a path, atualmente se adicionar um script python e ele não estiver na raiz do projeto o venv não sera executado, fortscript tenta executar com python padrão, mas da erro por não possuir os imports e a janela do terminal se encerra
392392

@@ -413,7 +413,8 @@ if __name__ == "__main__":
413413
- [x] Configuração via argumentos no código
414414
- [x] Callbacks de eventos (`on_pause` e `on_resume`)
415415
- [x] Níveis de log configuráveis (DEBUG, INFO, WARNING, ERROR)
416-
- [x] Encerramento seguro de processos (tree-kill)
416+
- [x] Encerramento seguro de processos (Graceful Shutdown + Kill)
417+
- [x] Monitoramento de saúde dos processos (Reinício automático em caso de falha)
417418
- [x] Adicionar opção de ativar ou desativar as janelas que aparecem dos scripts (Apenas em OS Windows)
418419
- [x] Type Hinting: Melhorar a tipagem em todos os métodos para melhor suporte em IDEs.
419420

src/fortscript/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .main import FortScript, RamConfig
1+
from .main import FortScript, RamConfig, Callbacks
22
from .games import GAMES
33

4-
__all__ = ['FortScript', 'RamConfig', 'GAMES']
4+
__all__ = ['FortScript', 'RamConfig', 'GAMES', 'Callbacks']

src/fortscript/main.py

Lines changed: 91 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,38 @@ def _start_project(self, project: ProjectConfig) -> None:
254254
def stop_scripts(self) -> None:
255255
"""Terminates active scripts and their child processes."""
256256
logger.info('Closing active scripts and their child processes...')
257+
258+
procs_to_kill = []
259+
260+
# 1. Collect all processes (parents and children)
257261
for proc in self.active_processes:
258262
try:
259-
# 1. Get the process by PID using psutil
260263
parent_process = psutil.Process(proc.pid)
264+
procs_to_kill.append(parent_process)
265+
procs_to_kill.extend(parent_process.children(recursive=True))
266+
except psutil.NoSuchProcess:
267+
pass
261268

262-
# 2. List all children (the python script, etc.)
263-
for child_process in parent_process.children(recursive=True):
264-
child_process.terminate() # Close the child
269+
# 2. Send terminate signal (SIGTERM / CTRL_C_EVENT equivalent attempt)
270+
for p in procs_to_kill:
271+
try:
272+
p.terminate()
273+
except psutil.NoSuchProcess:
274+
pass
265275

266-
# 3. Close the parent (the uv/pnpm command)
267-
parent_process.terminate()
276+
# 3. Wait for processes to exit (Graceful period)
277+
# We give them 3 seconds to close connections, save state, etc.
278+
gone, alive = psutil.wait_procs(procs_to_kill, timeout=3)
268279

269-
except (psutil.NoSuchProcess, Exception):
280+
# 4. Force kill if they are still alive
281+
for p in alive:
282+
try:
283+
logger.warning(
284+
f'Process {p.name()} (PID: {p.pid}) did not exit. '
285+
'Forcing kill.'
286+
)
287+
p.kill()
288+
except psutil.NoSuchProcess:
270289
pass
271290

272291
self.active_processes = []
@@ -285,61 +304,91 @@ def process_manager(self) -> None:
285304

286305
while True:
287306
status_dict = self.apps_monitoring.active_process_list()
288-
is_heavy_process_open = any(status_dict.values())
307+
is_heavy_open = any(status_dict.values())
289308

290309
current_ram = self.ram_monitoring.get_percent()
291310
is_ram_critical = current_ram > self.ram_config.safe
292311

293-
# Initial feedback if system is already heavy
294-
if first_check and (is_heavy_process_open or is_ram_critical):
295-
reason = (
296-
'heavy processes' if is_heavy_process_open else 'high RAM'
297-
)
312+
# Initial feedback
313+
if first_check and (is_heavy_open or is_ram_critical):
314+
reason = 'heavy processes' if is_heavy_open else 'high RAM'
298315
logger.info(
299316
f'System is busy ({reason}). Waiting for stabilization...'
300317
)
301318
first_check = False
302319

303-
if (is_heavy_process_open or is_ram_critical) and script_running:
304-
if is_heavy_process_open:
305-
detected = [k for k, v in status_dict.items() if v]
306-
logger.warning(
307-
f'Closing scripts due to heavy processes: {detected}'
308-
)
309-
else:
310-
logger.warning(
311-
'Closing scripts due to high RAM usage:'
312-
f'{current_ram}%'
313-
)
314-
315-
self.stop_scripts()
316-
logger.info('Scripts stopped.')
320+
# Stop Condition
321+
if (is_heavy_open or is_ram_critical) and script_running:
322+
self._handle_stop_condition(
323+
is_heavy_open, status_dict, current_ram
324+
)
317325
script_running = False
326+
327+
# Start Condition
318328
elif (
319-
not is_heavy_process_open
329+
not is_heavy_open
320330
and not is_ram_critical
321331
and not script_running
322332
and current_ram < self.ram_config.safe
323333
):
324-
logger.info(
325-
f'System stable (RAM: {current_ram}%). Starting scripts...'
326-
)
327-
self.start_scripts()
334+
self._handle_start_condition(current_ram)
328335
script_running = True
329336
first_check = False
330-
elif not is_heavy_process_open:
331-
# Optional: showing status even when already running,
332-
# or just pass
333-
pass
334337

335-
if not self.active_processes and script_running:
336-
logger.error(
337-
'No valid scripts found to start. '
338-
'FortScript is shutting down.'
339-
)
340-
break
338+
# Dead Process Handling
339+
if script_running and self.active_processes:
340+
script_running = self._check_dead_processes(script_running)
341+
341342
time.sleep(5)
342343

344+
def _handle_stop_condition(
345+
self,
346+
is_heavy_open: bool,
347+
status_dict: dict[str, bool],
348+
current_ram: float,
349+
) -> None:
350+
if is_heavy_open:
351+
detected = [k for k, v in status_dict.items() if v]
352+
logger.warning(
353+
f'Closing scripts due to heavy processes: {detected}'
354+
)
355+
else:
356+
logger.warning(
357+
f'Closing scripts due to high RAM usage: {current_ram}%'
358+
)
359+
360+
self.stop_scripts()
361+
logger.info('Scripts stopped.')
362+
363+
def _handle_start_condition(self, current_ram: float) -> None:
364+
logger.info(
365+
f'System stable (RAM: {current_ram}%). Starting scripts...'
366+
)
367+
self.start_scripts()
368+
369+
def _check_dead_processes(self, script_running: bool) -> bool:
370+
alive_processes = []
371+
for proc in self.active_processes:
372+
ret_code = proc.poll()
373+
if ret_code is None:
374+
alive_processes.append(proc)
375+
elif ret_code == 0:
376+
logger.info(
377+
f'Process (PID: {proc.pid}) finished successfully.'
378+
)
379+
else:
380+
logger.warning(
381+
f'Process (PID: {proc.pid}) crashed/exited '
382+
f'with code {ret_code}.'
383+
)
384+
385+
self.active_processes = alive_processes
386+
387+
if not self.active_processes:
388+
logger.info('All scripts finished. Waiting for system changes...')
389+
return False
390+
return script_running
391+
343392
def run(self) -> None:
344393
"""Runs the main application loop."""
345394
self.process_manager()

0 commit comments

Comments
 (0)