mirror of
https://github.com/jointakahe/takahe.git
synced 2024-11-25 08:41:00 +00:00
A few more tweaks for an initial deploy
This commit is contained in:
parent
8019311490
commit
2142677b01
8 changed files with 92 additions and 74 deletions
|
@ -16,4 +16,7 @@ RUN DJANGO_SETTINGS_MODULE=takahe.settings.development python3 manage.py collect
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Set some sensible defaults
|
||||||
|
ENV GUNICORN_CMD_ARGS="--workers 8"
|
||||||
|
|
||||||
CMD ["sh", "/takahe/docker/start.sh"]
|
CMD ["sh", "/takahe/docker/start.sh"]
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
python3 manage.py migrate
|
python3 manage.py migrate
|
||||||
|
|
||||||
exec gunicorn takahe.wsgi:application -w 8 -b 0.0.0.0:8000
|
exec gunicorn takahe.wsgi:application -b 0.0.0.0:8000
|
||||||
|
|
|
@ -608,6 +608,10 @@ form .button:hover {
|
||||||
|
|
||||||
/* Logged out homepage */
|
/* Logged out homepage */
|
||||||
|
|
||||||
|
.about p {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.about img.banner {
|
.about img.banner {
|
||||||
width: calc(100% + 30px);
|
width: calc(100% + 30px);
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
|
@ -10,7 +10,7 @@ from stator.runner import StatorRunner
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Runs a Stator runner for a short period"
|
help = "Runs a Stator runner"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -20,9 +20,30 @@ class Command(BaseCommand):
|
||||||
default=30,
|
default=30,
|
||||||
help="How many tasks to run at once",
|
help="How many tasks to run at once",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--liveness-file",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="A file to touch at least every 30 seconds to say the runner is alive",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--schedule-interval",
|
||||||
|
"-s",
|
||||||
|
type=int,
|
||||||
|
default=30,
|
||||||
|
help="How often to run cleaning and scheduling",
|
||||||
|
)
|
||||||
parser.add_argument("model_labels", nargs="*", type=str)
|
parser.add_argument("model_labels", nargs="*", type=str)
|
||||||
|
|
||||||
def handle(self, model_labels: List[str], concurrency: int, *args, **options):
|
def handle(
|
||||||
|
self,
|
||||||
|
model_labels: List[str],
|
||||||
|
concurrency: int,
|
||||||
|
liveness_file: str,
|
||||||
|
schedule_interval: int,
|
||||||
|
*args,
|
||||||
|
**options
|
||||||
|
):
|
||||||
# Cache system config
|
# Cache system config
|
||||||
Config.system = Config.load_system()
|
Config.system = Config.load_system()
|
||||||
# Resolve the models list into names
|
# Resolve the models list into names
|
||||||
|
@ -34,5 +55,10 @@ class Command(BaseCommand):
|
||||||
models = StatorModel.subclasses
|
models = StatorModel.subclasses
|
||||||
print("Running for models: " + " ".join(m._meta.label_lower for m in models))
|
print("Running for models: " + " ".join(m._meta.label_lower for m in models))
|
||||||
# Run a runner
|
# Run a runner
|
||||||
runner = StatorRunner(models, concurrency=concurrency)
|
runner = StatorRunner(
|
||||||
|
models,
|
||||||
|
concurrency=concurrency,
|
||||||
|
liveness_file=liveness_file,
|
||||||
|
schedule_interval=schedule_interval,
|
||||||
|
)
|
||||||
async_to_sync(runner.run)()
|
async_to_sync(runner.run)()
|
||||||
|
|
|
@ -3,7 +3,7 @@ import datetime
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from typing import List, Type
|
from typing import List, Optional, Type
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from stator.models import StatorModel
|
||||||
class StatorRunner:
|
class StatorRunner:
|
||||||
"""
|
"""
|
||||||
Runs tasks on models that are looking for state changes.
|
Runs tasks on models that are looking for state changes.
|
||||||
Designed to run in a one-shot mode, living inside a request.
|
Designed to run for a determinate amount of time, and then exit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -21,57 +21,63 @@ class StatorRunner:
|
||||||
models: List[Type[StatorModel]],
|
models: List[Type[StatorModel]],
|
||||||
concurrency: int = 50,
|
concurrency: int = 50,
|
||||||
concurrency_per_model: int = 10,
|
concurrency_per_model: int = 10,
|
||||||
run_period: int = 60,
|
liveness_file: Optional[str] = None,
|
||||||
wait_period: int = 30,
|
schedule_interval: int = 30,
|
||||||
|
lock_expiry: int = 300,
|
||||||
):
|
):
|
||||||
self.models = models
|
self.models = models
|
||||||
self.runner_id = uuid.uuid4().hex
|
self.runner_id = uuid.uuid4().hex
|
||||||
self.concurrency = concurrency
|
self.concurrency = concurrency
|
||||||
self.concurrency_per_model = concurrency_per_model
|
self.concurrency_per_model = concurrency_per_model
|
||||||
self.run_period = run_period
|
self.liveness_file = liveness_file
|
||||||
self.total_period = run_period + wait_period
|
self.schedule_interval = schedule_interval
|
||||||
|
self.lock_expiry = lock_expiry
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
start_time = time.monotonic()
|
|
||||||
self.handled = 0
|
self.handled = 0
|
||||||
|
self.last_clean = time.monotonic() - self.schedule_interval
|
||||||
self.tasks = []
|
self.tasks = []
|
||||||
# Clean up old locks
|
|
||||||
print("Running initial cleaning and scheduling")
|
|
||||||
initial_tasks = []
|
|
||||||
for model in self.models:
|
|
||||||
initial_tasks.append(model.atransition_clean_locks())
|
|
||||||
initial_tasks.append(model.atransition_schedule_due())
|
|
||||||
await asyncio.gather(*initial_tasks)
|
|
||||||
# For the first time period, launch tasks
|
# For the first time period, launch tasks
|
||||||
print("Running main task loop")
|
print("Running main task loop")
|
||||||
while (time.monotonic() - start_time) < self.run_period:
|
try:
|
||||||
self.remove_completed_tasks()
|
while True:
|
||||||
space_remaining = self.concurrency - len(self.tasks)
|
# Do we need to do cleaning?
|
||||||
# Fetch new tasks
|
if (time.monotonic() - self.last_clean) >= self.schedule_interval:
|
||||||
for model in self.models:
|
print(f"{self.handled} tasks processed so far")
|
||||||
if space_remaining > 0:
|
print("Running cleaning and scheduling")
|
||||||
for instance in await model.atransition_get_with_lock(
|
self.remove_completed_tasks()
|
||||||
number=min(space_remaining, self.concurrency_per_model),
|
for model in self.models:
|
||||||
lock_expiry=(
|
asyncio.create_task(model.atransition_clean_locks())
|
||||||
timezone.now()
|
asyncio.create_task(model.atransition_schedule_due())
|
||||||
+ datetime.timedelta(seconds=(self.total_period * 2) + 60)
|
self.last_clean = time.monotonic()
|
||||||
),
|
# Calculate space left for tasks
|
||||||
):
|
space_remaining = self.concurrency - len(self.tasks)
|
||||||
self.tasks.append(
|
# Fetch new tasks
|
||||||
asyncio.create_task(self.run_transition(instance))
|
for model in self.models:
|
||||||
)
|
if space_remaining > 0:
|
||||||
self.handled += 1
|
for instance in await model.atransition_get_with_lock(
|
||||||
space_remaining -= 1
|
number=min(space_remaining, self.concurrency_per_model),
|
||||||
# Prevent busylooping
|
lock_expiry=(
|
||||||
await asyncio.sleep(0.1)
|
timezone.now()
|
||||||
# Then wait for tasks to finish
|
+ datetime.timedelta(seconds=self.lock_expiry)
|
||||||
print("Waiting for tasks to complete")
|
),
|
||||||
while (time.monotonic() - start_time) < self.total_period:
|
):
|
||||||
self.remove_completed_tasks()
|
self.tasks.append(
|
||||||
if not self.tasks:
|
asyncio.create_task(self.run_transition(instance))
|
||||||
break
|
)
|
||||||
# Prevent busylooping
|
self.handled += 1
|
||||||
await asyncio.sleep(1)
|
space_remaining -= 1
|
||||||
|
# Prevent busylooping
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Wait for tasks to finish
|
||||||
|
print("Waiting for tasks to complete")
|
||||||
|
while True:
|
||||||
|
self.remove_completed_tasks()
|
||||||
|
if not self.tasks:
|
||||||
|
break
|
||||||
|
# Prevent busylooping
|
||||||
|
await asyncio.sleep(1)
|
||||||
print("Complete")
|
print("Complete")
|
||||||
return self.handled
|
return self.handled
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.http import HttpResponse, HttpResponseForbidden
|
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from stator.models import StatorModel
|
|
||||||
from stator.runner import StatorRunner
|
|
||||||
|
|
||||||
|
|
||||||
class RequestRunner(View):
|
|
||||||
"""
|
|
||||||
Runs a Stator runner within a HTTP request. For when you're on something
|
|
||||||
serverless.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def get(self, request):
|
|
||||||
# Check the token, if supplied
|
|
||||||
if settings.STATOR_TOKEN:
|
|
||||||
if request.GET.get("token") != settings.STATOR_TOKEN:
|
|
||||||
return HttpResponseForbidden()
|
|
||||||
# Run on all models
|
|
||||||
runner = StatorRunner(StatorModel.subclasses)
|
|
||||||
handled = await runner.run()
|
|
||||||
return HttpResponse(f"Handled {handled}")
|
|
|
@ -5,7 +5,6 @@ from django.views.static import serve
|
||||||
|
|
||||||
from activities.views import posts, search, timelines
|
from activities.views import posts, search, timelines
|
||||||
from core import views as core
|
from core import views as core
|
||||||
from stator import views as stator
|
|
||||||
from users.views import activitypub, admin, auth, follows, identity, settings
|
from users.views import activitypub, admin, auth, follows, identity, settings
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -110,8 +109,6 @@ urlpatterns = [
|
||||||
path(".well-known/host-meta", activitypub.HostMeta.as_view()),
|
path(".well-known/host-meta", activitypub.HostMeta.as_view()),
|
||||||
path(".well-known/nodeinfo", activitypub.NodeInfo.as_view()),
|
path(".well-known/nodeinfo", activitypub.NodeInfo.as_view()),
|
||||||
path("nodeinfo/2.0/", activitypub.NodeInfo2.as_view()),
|
path("nodeinfo/2.0/", activitypub.NodeInfo2.as_view()),
|
||||||
# Task runner
|
|
||||||
path(".stator/runner/", stator.RequestRunner.as_view()),
|
|
||||||
# Django admin
|
# Django admin
|
||||||
path("djadmin/", djadmin.site.urls),
|
path("djadmin/", djadmin.site.urls),
|
||||||
# Media files
|
# Media files
|
||||||
|
|
|
@ -39,7 +39,7 @@ class BasicSettings(AdminSettingsPage):
|
||||||
},
|
},
|
||||||
"site_about": {
|
"site_about": {
|
||||||
"title": "About This Site",
|
"title": "About This Site",
|
||||||
"help_text": "Displayed on the homepage and the about page",
|
"help_text": "Displayed on the homepage and the about page.\nNewlines are preserved; HTML also allowed.",
|
||||||
"display": "textarea",
|
"display": "textarea",
|
||||||
},
|
},
|
||||||
"site_icon": {
|
"site_icon": {
|
||||||
|
@ -67,6 +67,11 @@ class BasicSettings(AdminSettingsPage):
|
||||||
"help_text": "Shown above the signup form",
|
"help_text": "Shown above the signup form",
|
||||||
"display": "textarea",
|
"display": "textarea",
|
||||||
},
|
},
|
||||||
|
"restricted_usernames": {
|
||||||
|
"title": "Restricted Usernames",
|
||||||
|
"help_text": "Usernames that only admins can register for identities. One per line.",
|
||||||
|
"display": "textarea",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
layout = {
|
layout = {
|
||||||
|
@ -79,5 +84,5 @@ class BasicSettings(AdminSettingsPage):
|
||||||
],
|
],
|
||||||
"Signups": ["signup_allowed", "signup_invite_only", "signup_text"],
|
"Signups": ["signup_allowed", "signup_invite_only", "signup_text"],
|
||||||
"Posts": ["post_length"],
|
"Posts": ["post_length"],
|
||||||
"Identities": ["identity_max_per_user"],
|
"Identities": ["identity_max_per_user", "restricted_usernames"],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue