| Server IP : 142.11.234.102 / Your IP : 216.73.217.70 Web Server : Apache System : Linux dal-shared-66.hostwindsdns.com 4.18.0-513.24.1.lve.1.el8.x86_64 #1 SMP Thu May 9 15:10:09 UTC 2024 x86_64 User : krnuyqrm ( 1183) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /proc/self/root/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/internal/ |
Upload File : |
# -*- coding: utf-8 -*-
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT
import logging
import os
from dataclasses import dataclass
from typing import Optional
from xray import gettext as _
from .constants import fpm_stat_storage, fpm_reload_timeout
from .exceptions import XRayError
from .utils import dbm_storage, filelock, timestamp
logger = logging.getLogger('fpm_utils')
@dataclass
class FPMReloadController:
"""
Class to control the frequency of FPM service reloading
"""
fpm_service: str
@property
def latest_reload(self) -> Optional[int]:
"""
Get the timestamp of latest reload of FPM service
"""
try:
with dbm_storage(fpm_stat_storage) as _fpm_reload_stat:
try:
return int(_fpm_reload_stat[self.fpm_service].decode())
except KeyError:
return None
except RuntimeError as e:
raise XRayError(_('Failed to get the timestamp of latest FPM reload: %s') % str(e), flag='warning')
def save_latest_reload(self):
"""
Save the timestamp of latest reload of FPM service
"""
try:
# save timestamp of latest reload of corresponding service
with dbm_storage(fpm_stat_storage) as _fpm_reload_stat:
_fpm_reload_stat[self.fpm_service] = str(timestamp())
except RuntimeError as e:
raise XRayError(
_('Failed to save the timestamp of latest FPM reload %s, but main operation executed') % str(e),
flag='warning',
)
def delta(self) -> float:
"""
Get the time delta between current time and saved timestamp
"""
try:
# we store timestamp in seconds,
# but timeout -- fpm_reload_timeout -- in minutes
return (timestamp() - self.latest_reload) / 60
except (TypeError, XRayError):
# 1 hour,
# big enough for non-blocking flow in case of unexpected errors
return 60.0
def restrict(self) -> bool:
"""
Check if FPM reload should be blocked
"""
return self.delta() <= fpm_reload_timeout
def reserve_reload_slot(self) -> bool:
"""
Atomically decide whether THIS caller may perform the throttled reload
and, if so, reserve the slot before returning.
``restrict`` (the check) and ``save_latest_reload`` (the reserve) must
run as a single critical section shared across processes. Otherwise
several concurrent user-mode user-agent processes can each observe an
expired window, all pass the check, and each run the service reload --
preserving the server-wide DoS this throttle is meant to close (a tenant
who owns a mod_php DirectAdmin domain firing N parallel start/stop
requests triggers N global `service httpd restart`s). A cross-process
exclusive flock makes check-and-reserve atomic: at most one caller per
window records the timestamp and returns True; the losers re-read the
just-saved timestamp under the same lock and return False.
Reserve-before-act (the timestamp is saved BEFORE the reload runs, not
after) is deliberate: the reservation must be visible to concurrent
callers immediately, and a reload that later fails should still consume
the window (the throttle errs toward FEWER restarts). Root/operator
callers do not use this method -- they are never throttled.
Returns True if the slot was reserved (caller must proceed with the
reload); False if throttled (caller must skip). Any lock/storage failure
fails safe (returns False) rather than risk an unthrottled restart.
"""
lock_path = '%s.%s.lock' % (fpm_stat_storage, self.fpm_service)
try:
lock_fd = os.open(lock_path, os.O_CREAT | os.O_RDWR | os.O_CLOEXEC, 0o600)
except OSError as e:
logger.warning('Failed to open reload lock %s: %s', lock_path, e)
return False
try:
with filelock(lock_fd):
if self.restrict():
return False
self.save_latest_reload()
return True
except XRayError as e:
# filelock exhausted its retries, or save_latest_reload failed.
logger.warning('Failed to reserve reload slot for %s: %s', self.fpm_service, e)
return False
finally:
os.close(lock_fd)