mirror of
https://github.com/khoj-ai/khoj.git
synced 2025-02-17 08:04:21 +00:00
Disable Minutely Recurrence for Automations (#781)
* Disable automation recurrence at minute level frequency * Set a max lifetime for django's connections to the db * Disable any automation that has a non-numeric first digit (i.e., recuring on the minute level) * Re-enable automations --------- Co-authored-by: sabaimran <narmiabas@gmail.com>
This commit is contained in:
parent
5dca48d9fc
commit
3090b84252
4 changed files with 41 additions and 57 deletions
|
@ -2,16 +2,6 @@
|
|||
{% block content %}
|
||||
<div class="page">
|
||||
<div class="section">
|
||||
<div id="maintenance-notice">
|
||||
<div class="urgent-notice">
|
||||
<p>
|
||||
Hold up! Our Automations are taking a well-deserved break for some maintenance magic. They'll be back in action soon, better than ever. Thanks for your patience!
|
||||
</p>
|
||||
<div>
|
||||
<a class="maintenance-action" href="/">Back to Chat</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="section-title">
|
||||
<img class="card-icon" src="/static/assets/icons/automation.svg?v={{ khoj_version }}" alt="Automate">
|
||||
<span class="card-title-text">Automate (Preview)</span>
|
||||
|
@ -218,32 +208,6 @@
|
|||
grid-gap: 8px;
|
||||
}
|
||||
|
||||
div#maintenance-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
background-image: linear-gradient(to bottom, rgba(250, 130, 130, 0.8), rgba(255, 165, 0, 0.8));
|
||||
}
|
||||
|
||||
a.maintenance-action{
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.urgent-notice p,
|
||||
div.urgent-notice a {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.subject-wrapper p {
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -273,7 +237,7 @@
|
|||
}
|
||||
|
||||
</style>
|
||||
<!-- <script>
|
||||
<script>
|
||||
function deleteAutomation(automationId) {
|
||||
const AutomationList = document.getElementById("automations");
|
||||
fetch(`/api/automation?automation_id=${automationId}`, {
|
||||
|
@ -336,7 +300,7 @@
|
|||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
return response;
|
||||
})
|
||||
.then(automations => {
|
||||
notificationEl.style.display = 'block';
|
||||
|
@ -875,9 +839,9 @@
|
|||
}, 2000);
|
||||
})
|
||||
.catch(error => {
|
||||
notificationEl.textContent = `⚠️ Failed to ${actOn.toLowerCase()}e automations.`;
|
||||
notificationEl.textContent = `⚠️ Failed to ${actOn.toLowerCase()}e automation.`;
|
||||
notificationEl.style.display = "block";
|
||||
saveButtonEl.textContent = `⚠️ Failed to ${actOn.toLowerCase()}e automations`;
|
||||
saveButtonEl.textContent = `⚠️ Failed to ${actOn.toLowerCase()}e automation`;
|
||||
setTimeout(function() {
|
||||
saveButtonEl.textContent = `${actOn}e`;
|
||||
}, 2000);
|
||||
|
@ -915,5 +879,5 @@
|
|||
document.getElementById("automations").insertBefore(automationEl, document.getElementById("automations").firstChild);
|
||||
setupScheduleViewListener("* * * * *", placeholderId);
|
||||
})
|
||||
</script> -->
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -134,17 +134,17 @@ def run(should_start_server=True):
|
|||
poll_task_scheduler()
|
||||
|
||||
# Setup Background Scheduler
|
||||
# from django_apscheduler.jobstores import DjangoJobStore
|
||||
from django_apscheduler.jobstores import DjangoJobStore
|
||||
|
||||
# state.scheduler = BackgroundScheduler(
|
||||
# {
|
||||
# "apscheduler.timezone": "UTC",
|
||||
# "apscheduler.job_defaults.misfire_grace_time": "60", # Useful to run scheduled jobs even when worker delayed because it was busy or down
|
||||
# "apscheduler.job_defaults.coalesce": "true", # Combine multiple jobs into one if they are scheduled at the same time
|
||||
# }
|
||||
# )
|
||||
# state.scheduler.add_jobstore(DjangoJobStore(), "default")
|
||||
# state.scheduler.start()
|
||||
state.scheduler = BackgroundScheduler(
|
||||
{
|
||||
"apscheduler.timezone": "UTC",
|
||||
"apscheduler.job_defaults.misfire_grace_time": "60", # Useful to run scheduled jobs even when worker delayed because it was busy or down
|
||||
"apscheduler.job_defaults.coalesce": "true", # Combine multiple jobs into one if they are scheduled at the same time
|
||||
}
|
||||
)
|
||||
state.scheduler.add_jobstore(DjangoJobStore(), "default")
|
||||
state.scheduler.start()
|
||||
|
||||
# Start Server
|
||||
configure_routes(app)
|
||||
|
|
|
@ -6,6 +6,7 @@ import os
|
|||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from random import random
|
||||
from typing import Any, Callable, List, Optional, Union
|
||||
|
||||
import cron_descriptor
|
||||
|
@ -467,8 +468,15 @@ async def post_automation(
|
|||
crontime = " ".join(crontime.split(" ")[:5])
|
||||
# Convert crontime to standard unix crontime
|
||||
crontime = crontime.replace("?", "*")
|
||||
if crontime == "* * * * *":
|
||||
return Response(content="Invalid crontime. Please create a more specific schedule.", status_code=400)
|
||||
|
||||
# Disallow minute level automation recurrence
|
||||
minute_value = crontime.split(" ")[0]
|
||||
if not minute_value.isdigit():
|
||||
return Response(
|
||||
content="Recurrence of every X minutes is unsupported. Please create a less frequent schedule.",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
subject = await acreate_title_from_query(q)
|
||||
|
||||
# Create new Conversation Session associated with this new task
|
||||
|
@ -560,6 +568,14 @@ def edit_job(
|
|||
# Convert crontime to standard unix crontime
|
||||
crontime = crontime.replace("?", "*")
|
||||
|
||||
# Disallow minute level automation recurrence
|
||||
minute_value = crontime.split(" ")[0]
|
||||
if not minute_value.isdigit():
|
||||
return Response(
|
||||
content="Recurrence of every X minutes is unsupported. Please create a less frequent schedule.",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# Construct updated automation metadata
|
||||
automation_metadata = json.loads(automation.name)
|
||||
automation_metadata["scheduling_request"] = q
|
||||
|
|
|
@ -4,10 +4,12 @@ import hashlib
|
|||
import io
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from functools import partial
|
||||
from random import random
|
||||
from typing import (
|
||||
Annotated,
|
||||
Any,
|
||||
|
@ -1014,8 +1016,6 @@ def scheduled_chat(
|
|||
|
||||
async def create_automation(q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}):
|
||||
crontime, query_to_run, subject = await schedule_query(q, meta_log)
|
||||
if crontime == "* * * * *":
|
||||
raise HTTPException(status_code=400, detail="Cannot run jobs constantly. Please provide a valid crontime.")
|
||||
job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url)
|
||||
return job, crontime, query_to_run, subject
|
||||
|
||||
|
@ -1029,9 +1029,13 @@ async def schedule_automation(
|
|||
user: KhojUser,
|
||||
calling_url: URL,
|
||||
):
|
||||
# Disable minute level automation recurrence
|
||||
minute_value = crontime.split(" ")[0]
|
||||
if not minute_value.isdigit():
|
||||
# Run automation at some random minute (to distribute request load) instead of running every X minutes
|
||||
crontime = " ".join([str(math.floor(random() * 60))] + crontime.split(" ")[1:])
|
||||
|
||||
user_timezone = pytz.timezone(timezone)
|
||||
if crontime == "* * * * *":
|
||||
raise HTTPException(status_code=400, detail="Cannot run jobs constantly. Please provide a valid crontime.")
|
||||
trigger = CronTrigger.from_crontab(crontime, user_timezone)
|
||||
trigger.jitter = 60
|
||||
# Generate id and metadata used by task scheduler and process locks for the task runs
|
||||
|
|
Loading…
Add table
Reference in a new issue