mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-12-28 08:02:11 +00:00
增加部分缓存
This commit is contained in:
parent
482a1a7d02
commit
7df98245e4
@ -1,13 +1,92 @@
|
||||
import functools
|
||||
import os
|
||||
from django.core.cache import cache
|
||||
import threading
|
||||
import time
|
||||
|
||||
from django.db import transaction, IntegrityError
|
||||
|
||||
from utils.constants import CacheKey
|
||||
from utils.shortcuts import rand_str
|
||||
from judge.languages import languages
|
||||
from .models import SysOptions as SysOptionsModel
|
||||
|
||||
|
||||
class my_property:
|
||||
"""
|
||||
在 metaclass 中使用,以实现:
|
||||
1. ttl = None,不缓存
|
||||
2. ttl is callable,条件缓存
|
||||
3. 缓存 ttl 秒
|
||||
"""
|
||||
def __init__(self, func=None, fset=None, ttl=None):
|
||||
self.fset = fset
|
||||
self.local = threading.local()
|
||||
self.ttl = ttl
|
||||
self._check_ttl(ttl)
|
||||
self.func = func
|
||||
functools.update_wrapper(self, func)
|
||||
|
||||
def _check_ttl(self, value):
|
||||
if value is None or callable(value):
|
||||
return
|
||||
return self._check_timeout(value)
|
||||
|
||||
def _check_timeout(self, value):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError(f"Invalid timeout type: {type(value)}")
|
||||
if value < 0:
|
||||
raise ValueError("Invalid timeout value, it must >= 0")
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
|
||||
now = time.time()
|
||||
if self.ttl:
|
||||
if hasattr(self.local, "value"):
|
||||
value, expire_at = self.local.value
|
||||
if now < expire_at:
|
||||
return value
|
||||
|
||||
value = self.func(obj)
|
||||
|
||||
# 如果定义了条件缓存, ttl 是一个函数,返回要缓存多久;返回 0 代表不要缓存
|
||||
if callable(self.ttl):
|
||||
# 而且条件缓存说不要缓存,那就直接返回,不要设置 local
|
||||
timeout = self.ttl(value)
|
||||
self._check_timeout(timeout)
|
||||
|
||||
if timeout == 0:
|
||||
return value
|
||||
elif timeout > 0:
|
||||
self.local.value = (value, now + timeout)
|
||||
else:
|
||||
# ttl 是一个数字
|
||||
self.local.value = (value, now + self.ttl)
|
||||
return value
|
||||
else:
|
||||
return self.func(obj)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if not self.fset:
|
||||
raise AttributeError("can't set attribute")
|
||||
self.fset(obj, value)
|
||||
if hasattr(self.local, "value"):
|
||||
del self.local.value
|
||||
|
||||
def setter(self, func):
|
||||
self.fset = func
|
||||
return self
|
||||
|
||||
def __call__(self, func, *args, **kwargs) -> "my_property":
|
||||
if self.func is None:
|
||||
self.func = func
|
||||
functools.update_wrapper(self, func)
|
||||
return self
|
||||
|
||||
|
||||
DEFAULT_SHORT_TTL = 2
|
||||
|
||||
|
||||
def default_token():
|
||||
token = os.environ.get("JUDGE_SERVER_TOKEN")
|
||||
return token if token else rand_str()
|
||||
@ -41,23 +120,10 @@ class OptionDefaultValue:
|
||||
|
||||
|
||||
class _SysOptionsMeta(type):
|
||||
@classmethod
|
||||
def _set_cache(mcs, option_key, option_value):
|
||||
cache.set(f"{CacheKey.option}:{option_key}", option_value, timeout=60)
|
||||
|
||||
@classmethod
|
||||
def _del_cache(mcs, option_key):
|
||||
cache.delete(f"{CacheKey.option}:{option_key}")
|
||||
|
||||
@classmethod
|
||||
def _get_keys(cls):
|
||||
return [key for key in OptionKeys.__dict__ if not key.startswith("__")]
|
||||
|
||||
def rebuild_cache(cls):
|
||||
for key in cls._get_keys():
|
||||
# get option 的时候会写 cache 的
|
||||
cls._get_option(key, use_cache=False)
|
||||
|
||||
@classmethod
|
||||
def _init_option(mcs):
|
||||
for item in mcs._get_keys():
|
||||
@ -71,19 +137,14 @@ class _SysOptionsMeta(type):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _get_option(mcs, option_key, use_cache=True):
|
||||
def _get_option(mcs, option_key):
|
||||
try:
|
||||
if use_cache:
|
||||
option = cache.get(f"{CacheKey.option}:{option_key}")
|
||||
if option:
|
||||
return option
|
||||
option = SysOptionsModel.objects.get(key=option_key)
|
||||
value = option.value
|
||||
mcs._set_cache(option_key, value)
|
||||
return value
|
||||
except SysOptionsModel.DoesNotExist:
|
||||
mcs._init_option()
|
||||
return mcs._get_option(option_key, use_cache=use_cache)
|
||||
return mcs._get_option(option_key)
|
||||
|
||||
@classmethod
|
||||
def _set_option(mcs, option_key: str, option_value):
|
||||
@ -92,7 +153,6 @@ class _SysOptionsMeta(type):
|
||||
option = SysOptionsModel.objects.select_for_update().get(key=option_key)
|
||||
option.value = option_value
|
||||
option.save()
|
||||
mcs._del_cache(option_key)
|
||||
except SysOptionsModel.DoesNotExist:
|
||||
mcs._init_option()
|
||||
mcs._set_option(option_key, option_value)
|
||||
@ -105,7 +165,6 @@ class _SysOptionsMeta(type):
|
||||
value = option.value + 1
|
||||
option.value = value
|
||||
option.save()
|
||||
mcs._del_cache(option_key)
|
||||
except SysOptionsModel.DoesNotExist:
|
||||
mcs._init_option()
|
||||
return mcs._increment(option_key)
|
||||
@ -122,7 +181,7 @@ class _SysOptionsMeta(type):
|
||||
result[key] = mcs._get_option(key)
|
||||
return result
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def website_base_url(cls):
|
||||
return cls._get_option(OptionKeys.website_base_url)
|
||||
|
||||
@ -130,7 +189,7 @@ class _SysOptionsMeta(type):
|
||||
def website_base_url(cls, value):
|
||||
cls._set_option(OptionKeys.website_base_url, value)
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def website_name(cls):
|
||||
return cls._get_option(OptionKeys.website_name)
|
||||
|
||||
@ -138,7 +197,7 @@ class _SysOptionsMeta(type):
|
||||
def website_name(cls, value):
|
||||
cls._set_option(OptionKeys.website_name, value)
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def website_name_shortcut(cls):
|
||||
return cls._get_option(OptionKeys.website_name_shortcut)
|
||||
|
||||
@ -146,7 +205,7 @@ class _SysOptionsMeta(type):
|
||||
def website_name_shortcut(cls, value):
|
||||
cls._set_option(OptionKeys.website_name_shortcut, value)
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def website_footer(cls):
|
||||
return cls._get_option(OptionKeys.website_footer)
|
||||
|
||||
@ -154,7 +213,7 @@ class _SysOptionsMeta(type):
|
||||
def website_footer(cls, value):
|
||||
cls._set_option(OptionKeys.website_footer, value)
|
||||
|
||||
@property
|
||||
@my_property
|
||||
def allow_register(cls):
|
||||
return cls._get_option(OptionKeys.allow_register)
|
||||
|
||||
@ -162,7 +221,7 @@ class _SysOptionsMeta(type):
|
||||
def allow_register(cls, value):
|
||||
cls._set_option(OptionKeys.allow_register, value)
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def submission_list_show_all(cls):
|
||||
return cls._get_option(OptionKeys.submission_list_show_all)
|
||||
|
||||
@ -170,7 +229,7 @@ class _SysOptionsMeta(type):
|
||||
def submission_list_show_all(cls, value):
|
||||
cls._set_option(OptionKeys.submission_list_show_all, value)
|
||||
|
||||
@property
|
||||
@my_property
|
||||
def smtp_config(cls):
|
||||
return cls._get_option(OptionKeys.smtp_config)
|
||||
|
||||
@ -178,7 +237,7 @@ class _SysOptionsMeta(type):
|
||||
def smtp_config(cls, value):
|
||||
cls._set_option(OptionKeys.smtp_config, value)
|
||||
|
||||
@property
|
||||
@my_property
|
||||
def judge_server_token(cls):
|
||||
return cls._get_option(OptionKeys.judge_server_token)
|
||||
|
||||
@ -186,7 +245,7 @@ class _SysOptionsMeta(type):
|
||||
def judge_server_token(cls, value):
|
||||
cls._set_option(OptionKeys.judge_server_token, value)
|
||||
|
||||
@property
|
||||
@my_property
|
||||
def throttling(cls):
|
||||
return cls._get_option(OptionKeys.throttling)
|
||||
|
||||
@ -194,7 +253,7 @@ class _SysOptionsMeta(type):
|
||||
def throttling(cls, value):
|
||||
cls._set_option(OptionKeys.throttling, value)
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def languages(cls):
|
||||
return cls._get_option(OptionKeys.languages)
|
||||
|
||||
@ -202,15 +261,15 @@ class _SysOptionsMeta(type):
|
||||
def languages(cls, value):
|
||||
cls._set_option(OptionKeys.languages, value)
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def spj_languages(cls):
|
||||
return [item for item in cls.languages if "spj" in item]
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def language_names(cls):
|
||||
return [item["name"] for item in languages]
|
||||
|
||||
@property
|
||||
@my_property(ttl=DEFAULT_SHORT_TTL)
|
||||
def spj_language_names(cls):
|
||||
return [item["name"] for item in cls.languages if "spj" in item]
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import re
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
TEMPLATE_BASE = """//PREPEND BEGIN
|
||||
{}
|
||||
@ -13,6 +15,7 @@ TEMPLATE_BASE = """//PREPEND BEGIN
|
||||
//APPEND END"""
|
||||
|
||||
|
||||
@lru_cache(maxsize=100)
|
||||
def parse_problem_template(template_str):
|
||||
prepend = re.findall(r"//PREPEND BEGIN\n([\s\S]+?)//PREPEND END", template_str)
|
||||
template = re.findall(r"//TEMPLATE BEGIN\n([\s\S]+?)//TEMPLATE END", template_str)
|
||||
@ -22,5 +25,6 @@ def parse_problem_template(template_str):
|
||||
"append": append[0] if append else ""}
|
||||
|
||||
|
||||
@lru_cache(maxsize=100)
|
||||
def build_problem_template(prepend, template, append):
|
||||
return TEMPLATE_BASE.format(prepend, template, append)
|
||||
|
@ -25,7 +25,6 @@ class CacheKey:
|
||||
waiting_queue = "waiting_queue"
|
||||
contest_rank_cache = "contest_rank_cache"
|
||||
website_config = "website_config"
|
||||
option = "option"
|
||||
|
||||
|
||||
class Difficulty(Choices):
|
||||
|
Loading…
Reference in New Issue
Block a user