File size: 3,973 Bytes
d5b7ee9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Entry point — run with `trading-cli` or `uv run trading-cli`."""

import os
import sys

# CRITICAL: Lower file descriptor limit EARLY to avoid subprocess fds_to_keep error
# Must be set BEFORE importing transformers or any library that uses subprocess
try:
    import resource
    soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
    # Lower to 1024 to avoid fds_to_keep errors while still allowing normal operation
    target_limit = 1024
    if soft > target_limit:
        new_soft = min(target_limit, hard)
        resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft, hard))
        print(f"Adjusted FD limit: {soft} -> {new_soft}", file=sys.stderr)
except Exception as e:
    print(f"Could not adjust FD limit: {e}", file=sys.stderr)

# CRITICAL: Disable all parallelism before importing transformers
# These MUST be set before any transformers/tokenizers import
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
os.environ['TRANSFORMERS_VERBOSITY'] = 'error'
os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1'
os.environ['TQDM_DISABLE'] = '1'

import logging
import signal
import threading
import time
from datetime import datetime
from pathlib import Path


def main() -> None:
    # Ensure config and log directories exist before any file operations
    config_dir = Path("~/.config/trading-cli").expanduser()
    config_dir.mkdir(parents=True, exist_ok=True)

    # Create a new log file per run, keep only the last 10
    log_path = config_dir / f"app-{datetime.now().strftime('%Y%m%d-%H%M%S')}.log"
    logging.basicConfig(
        level=logging.WARNING,
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
        handlers=[
            logging.FileHandler(
                log_path,
                mode="w",
                encoding="utf-8",
            )
        ],
    )

    # Clean up old log files (keep last 10)
    try:
        log_files = sorted(config_dir.glob("app-*.log"))
        for old_log in log_files[:-10]:
            old_log.unlink()
    except Exception:
        pass
    from trading_cli.app import TradingApp

    app = TradingApp()

    # Track if we've already started shutdown
    _shutdown_started = False
    _shutdown_lock = threading.Lock()

    def force_kill():
        """Force kill after timeout."""
        time.sleep(3)
        print("\n⚠️  Force-killing process (shutdown timeout exceeded)", file=sys.stderr)
        os._exit(1)  # Force kill, bypassing all handlers

    def handle_sigint(signum, frame):
        """Handle SIGINT (Ctrl+C) with force-kill fallback."""
        nonlocal _shutdown_started

        with _shutdown_lock:
            if _shutdown_started:
                # Already shutting down, skip force kill
                print("\n⚠️  Already shutting down, waiting...", file=sys.stderr)
                return

            _shutdown_started = True
            logger = logging.getLogger(__name__)
            logger.info("Received SIGINT (Ctrl+C), initiating shutdown...")
            print("\n🛑 Shutting down... (press Ctrl+C again to force-kill)", file=sys.stderr)

            # Start force-kill timer
            killer_thread = threading.Thread(target=force_kill, daemon=True)
            killer_thread.start()

            # Try clean shutdown
            try:
                app.exit()
            except Exception as e:
                logger.error(f"Error during exit: {e}")
            finally:
                # Give it a moment then exit
                time.sleep(0.5)
                sys.exit(0)

    signal.signal(signal.SIGINT, handle_sigint)

    try:
        app.run()
    except KeyboardInterrupt:
        # This handles the case where Textual catches it first
        logging.getLogger(__name__).info("KeyboardInterrupt caught at top level, exiting...")
        sys.exit(0)
    finally:
        # Ensure clean shutdown
        logging.getLogger(__name__).info("Trading CLI shutdown complete")
        sys.exit(0)


if __name__ == "__main__":
    main()