|
4 | 4 | :copyright: (c) 2022 by Netflix Inc., see AUTHORS for more
|
5 | 5 | :license: Apache, see LICENSE for more details.
|
6 | 6 | """
|
| 7 | +from datetime import datetime, timedelta, timezone |
7 | 8 | import logging
|
| 9 | +import queue |
| 10 | +from sqlalchemy import asc |
| 11 | +from sqlalchemy.orm import scoped_session |
| 12 | + |
8 | 13 | from schedule import every
|
9 |
| -from dispatch.database.core import SessionLocal |
| 14 | +from dispatch.database.core import SessionLocal, sessionmaker, engine |
10 | 15 | from dispatch.scheduler import scheduler
|
11 | 16 | from dispatch.project.models import Project
|
12 | 17 | from dispatch.plugin import service as plugin_service
|
13 | 18 | from dispatch.signal import flows as signal_flows
|
14 |
| -from dispatch.decorators import scheduled_project_task |
| 19 | +from dispatch.decorators import scheduled_project_task, timer |
15 | 20 | from dispatch.signal.models import SignalInstance
|
16 | 21 |
|
17 | 22 | log = logging.getLogger(__name__)
|
@@ -47,27 +52,77 @@ def consume_signals(db_session: SessionLocal, project: Project):
|
47 | 52 | log.debug(signal_instance_data)
|
48 | 53 | log.exception(e)
|
49 | 54 |
|
50 |
| - if signal_instances: |
51 |
| - plugin.instance.delete() |
52 | 55 |
|
| 56 | +@timer |
| 57 | +def process_signal_instance(db_session: SessionLocal, signal_instance: SignalInstance) -> None: |
| 58 | + try: |
| 59 | + signal_flows.signal_instance_create_flow( |
| 60 | + db_session=db_session, |
| 61 | + signal_instance_id=signal_instance.id, |
| 62 | + ) |
| 63 | + except Exception as e: |
| 64 | + log.debug(signal_instance) |
| 65 | + log.exception(e) |
| 66 | + finally: |
| 67 | + db_session.close() |
| 68 | + |
| 69 | + |
| 70 | +MAX_SIGNAL_INSTANCES = 500 |
| 71 | +signal_queue = queue.Queue(maxsize=MAX_SIGNAL_INSTANCES) |
53 | 72 |
|
| 73 | + |
| 74 | +@timer |
54 | 75 | @scheduler.add(every(1).minutes, name="signal-process")
|
55 | 76 | @scheduled_project_task
|
56 | 77 | def process_signals(db_session: SessionLocal, project: Project):
|
57 |
| - """Processes signals and create cases if appropriate.""" |
| 78 | + """ |
| 79 | + Process signals and create cases if appropriate. |
| 80 | +
|
| 81 | + This function processes signals within a given project, creating cases if necessary. |
| 82 | + It runs every minute, processing signals that meet certain criteria within the last 5 minutes. |
| 83 | + Signals are added to a queue for processing, and then each signal instance is processed. |
| 84 | +
|
| 85 | + Args: |
| 86 | + db_session: The database session used to query and update the database. |
| 87 | + project: The project for which the signals will be processed. |
| 88 | +
|
| 89 | + Notes: |
| 90 | + The function is decorated with three decorators: |
| 91 | + - scheduler.add: schedules the function to run every minute. |
| 92 | + - scheduled_project_task: ensures that the function is executed as a scheduled project task. |
| 93 | +
|
| 94 | + The function uses a queue to process signal instances in a first-in-first-out (FIFO) order |
| 95 | + This ensures that signals are processed in the order they were added to the queue. |
| 96 | +
|
| 97 | + A scoped session is used to create a new database session for each signal instance |
| 98 | + This ensures that each signal instance is processed using its own separate database connection, |
| 99 | + preventing potential issues with concurrent connections. |
| 100 | + """ |
| 101 | + one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1) |
58 | 102 | signal_instances = (
|
59 |
| - db_session.query(SignalInstance) |
60 |
| - .filter(SignalInstance.project_id == project.id) |
61 |
| - .filter(SignalInstance.filter_action == None) # noqa |
62 |
| - .filter(SignalInstance.case_id == None) # noqa |
63 |
| - ).limit(100) |
| 103 | + ( |
| 104 | + db_session.query(SignalInstance) |
| 105 | + .filter(SignalInstance.project_id == project.id) |
| 106 | + .filter(SignalInstance.filter_action == None) # noqa |
| 107 | + .filter(SignalInstance.case_id == None) # noqa |
| 108 | + .filter(SignalInstance.created_at >= one_hour_ago) |
| 109 | + ) |
| 110 | + .order_by(asc(SignalInstance.created_at)) |
| 111 | + .limit(MAX_SIGNAL_INSTANCES) |
| 112 | + ) |
| 113 | + # Add each signal_instance to the queue for processing |
64 | 114 | for signal_instance in signal_instances:
|
65 |
| - log.info(f"Attempting to process the following signal: {signal_instance.id}") |
66 |
| - try: |
67 |
| - signal_flows.signal_instance_create_flow( |
68 |
| - db_session=db_session, |
69 |
| - signal_instance_id=signal_instance.id, |
70 |
| - ) |
71 |
| - except Exception as e: |
72 |
| - log.debug(signal_instance) |
73 |
| - log.exception(e) |
| 115 | + signal_queue.put(signal_instance) |
| 116 | + |
| 117 | + schema_engine = engine.execution_options( |
| 118 | + schema_translate_map={ |
| 119 | + None: "dispatch_organization_default", |
| 120 | + } |
| 121 | + ) |
| 122 | + session = scoped_session(sessionmaker(bind=schema_engine)) |
| 123 | + |
| 124 | + # Process each signal instance in the queue |
| 125 | + while not signal_queue.empty(): |
| 126 | + signal_instance = signal_queue.get() |
| 127 | + db_session = session() |
| 128 | + process_signal_instance(db_session, signal_instance) |
0 commit comments