feat: create TaskManager singleton class (#3775)
Co-authored-by: dadofsambonzuki <dadofsambonzuki@users.noreply.github.com>
This commit is contained in:
@@ -6,7 +6,6 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from asyncio.tasks import create_task
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -21,6 +20,7 @@ from lnbits.helpers import (
|
||||
version_parse,
|
||||
)
|
||||
from lnbits.settings import settings
|
||||
from lnbits.task_manager import task_manager
|
||||
from lnbits.utils.cache import cache
|
||||
|
||||
|
||||
@@ -642,7 +642,10 @@ class InstallableExtension(BaseModel):
|
||||
|
||||
if cache_value.older_than(10 * 60) or post_refresh_cache:
|
||||
# refresh cache in background if older than 10 minutes or requested
|
||||
create_task(cls._refresh_installable_extensions_cache())
|
||||
task_manager.create_task(
|
||||
cls._refresh_installable_extensions_cache(),
|
||||
"refresh_installable_extensions_cache",
|
||||
)
|
||||
|
||||
extension_list = cache_value.value # type: ignore
|
||||
return extension_list
|
||||
|
||||
@@ -20,6 +20,7 @@ from lnbits.fiat.base import (
|
||||
FiatPaymentSuccessStatus,
|
||||
)
|
||||
from lnbits.settings import settings
|
||||
from lnbits.task_manager import task_manager
|
||||
|
||||
|
||||
async def handle_fiat_payment_confirmation(
|
||||
@@ -60,11 +61,7 @@ async def check_fiat_status(payment: Payment) -> FiatPaymentStatus:
|
||||
payment.status = PaymentState.SUCCESS.value
|
||||
await update_payment(payment)
|
||||
await handle_fiat_payment_confirmation(payment)
|
||||
|
||||
# notify receivers asynchronously
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
|
||||
await internal_invoice_queue.put(payment.checking_id)
|
||||
task_manager.internal_invoice_queue.put_nowait(payment)
|
||||
|
||||
return fiat_status
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ async def check_server_balance_against_node():
|
||||
|
||||
|
||||
async def check_balance_delta_changed():
|
||||
if settings.notification_balance_delta_threshold_sats <= 0:
|
||||
return
|
||||
status = await get_balance_delta()
|
||||
if settings.latest_balance_delta_sats is None:
|
||||
settings.latest_balance_delta_sats = status.delta_sats
|
||||
|
||||
@@ -237,6 +237,15 @@ async def send_email(
|
||||
return False
|
||||
|
||||
|
||||
async def dispatch_payment_notification(payment: Payment) -> None:
|
||||
"""
|
||||
This worker dispatches the payment notifications.
|
||||
"""
|
||||
wallet = await get_wallet(payment.wallet_id)
|
||||
if wallet:
|
||||
await send_payment_notification(wallet, payment)
|
||||
|
||||
|
||||
async def dispatch_webhook(payment: Payment):
|
||||
"""
|
||||
Dispatches the webhook to the webhook url.
|
||||
|
||||
@@ -19,6 +19,7 @@ from lnbits.exceptions import InvoiceError, PaymentError, UnsupportedError
|
||||
from lnbits.fiat import get_fiat_provider
|
||||
from lnbits.helpers import check_callback_url
|
||||
from lnbits.settings import settings
|
||||
from lnbits.task_manager import task_manager
|
||||
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash, verify_preimage
|
||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
||||
from lnbits.wallets import fake_wallet, get_funding_source
|
||||
@@ -516,9 +517,7 @@ async def update_wallet_balance(
|
||||
)
|
||||
payment.status = PaymentState.SUCCESS
|
||||
await update_payment(payment, conn=conn)
|
||||
from lnbits.tasks import internal_invoice_queue_put
|
||||
|
||||
await internal_invoice_queue_put(payment.checking_id)
|
||||
task_manager.internal_invoice_queue.put_nowait(payment)
|
||||
|
||||
|
||||
async def check_wallet_limits(
|
||||
@@ -789,10 +788,8 @@ async def _pay_internal_invoice(
|
||||
) # notify the receiver
|
||||
|
||||
# notify receiver asynchronously (extension listeners)
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
|
||||
logger.debug(f"enqueuing internal invoice {internal_payment.checking_id}")
|
||||
await internal_invoice_queue.put(internal_payment.checking_id)
|
||||
task_manager.internal_invoice_queue.put_nowait(internal_payment)
|
||||
|
||||
return payment
|
||||
|
||||
@@ -829,16 +826,15 @@ async def _pay_external_invoice(
|
||||
|
||||
fee_reserve_msat = fee_reserve(amount_msat, internal=False)
|
||||
|
||||
from lnbits.tasks import create_task
|
||||
|
||||
task = create_task(
|
||||
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat)
|
||||
task = task_manager.create_task(
|
||||
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat),
|
||||
f"fundingsource_pay_invoice_{checking_id}",
|
||||
)
|
||||
|
||||
# make sure a hold invoice or deferred payment is not blocking the server
|
||||
wait_time = max(1, settings.lnbits_funding_source_pay_invoice_wait_seconds)
|
||||
try:
|
||||
payment_response = await asyncio.wait_for(task, timeout=wait_time)
|
||||
payment_response = await asyncio.wait_for(task.task, timeout=wait_time)
|
||||
except asyncio.TimeoutError:
|
||||
# return pending payment on timeout
|
||||
logger.debug(
|
||||
@@ -1108,3 +1104,18 @@ async def update_invoice_from_paid_invoices_stream(checking_id: str) -> Payment
|
||||
payment = await update_payment(payment)
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
async def fundingsource_invoice_producer() -> None:
|
||||
"""
|
||||
will collect all invoices that come directly from the backend wallet.
|
||||
|
||||
Called registered in the app startup sequence and run by taskmanager.
|
||||
"""
|
||||
funding_source = get_funding_source()
|
||||
async for checking_id in funding_source.paid_invoices_stream():
|
||||
logger.info(f"got a payment notification {checking_id}")
|
||||
payment = await update_invoice_from_paid_invoices_stream(checking_id)
|
||||
if payment:
|
||||
logger.success(f"fundingsource invoice {checking_id} settled")
|
||||
task_manager.invoice_queue.put_nowait(payment)
|
||||
|
||||
+45
-139
@@ -2,26 +2,16 @@ import asyncio
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.crud import (
|
||||
create_audit_entry,
|
||||
get_wallet,
|
||||
)
|
||||
from lnbits.core.crud.audit import delete_expired_audit_entries
|
||||
from lnbits.core.crud import create_audit_entry
|
||||
from lnbits.core.crud.payments import get_payments_status_count
|
||||
from lnbits.core.crud.users import get_accounts
|
||||
from lnbits.core.crud.wallets import get_wallets_count
|
||||
from lnbits.core.models.audit import AuditEntry
|
||||
from lnbits.core.models.extensions import InstallableExtension
|
||||
from lnbits.core.models.notifications import NotificationType
|
||||
from lnbits.core.services.funding_source import (
|
||||
check_balance_delta_changed,
|
||||
check_server_balance_against_node,
|
||||
get_balance_delta,
|
||||
)
|
||||
from lnbits.core.services.funding_source import get_balance_delta
|
||||
from lnbits.core.services.notifications import (
|
||||
enqueue_admin_notification,
|
||||
process_next_notification,
|
||||
send_payment_notification,
|
||||
)
|
||||
from lnbits.db import Filters
|
||||
from lnbits.settings import settings
|
||||
@@ -31,48 +21,24 @@ from lnbits.utils.exchange_rates import btc_price_from_aggregator, btc_rates
|
||||
audit_queue: asyncio.Queue[AuditEntry] = asyncio.Queue()
|
||||
|
||||
|
||||
async def run_by_the_minute_tasks() -> None:
|
||||
minute_counter = 0
|
||||
while settings.lnbits_running:
|
||||
status_minutes = settings.lnbits_notification_server_status_hours * 60
|
||||
|
||||
if settings.notification_balance_delta_threshold_sats > 0:
|
||||
try:
|
||||
# runs by default every minute, the delta should not change that often
|
||||
await check_balance_delta_changed()
|
||||
except Exception as ex:
|
||||
logger.error(ex)
|
||||
|
||||
if minute_counter % settings.lnbits_watchdog_interval_minutes == 0:
|
||||
try:
|
||||
await check_server_balance_against_node()
|
||||
except Exception as ex:
|
||||
logger.error(ex)
|
||||
|
||||
if minute_counter % status_minutes == 0:
|
||||
try:
|
||||
await _notify_server_status()
|
||||
except Exception as ex:
|
||||
logger.error(ex)
|
||||
|
||||
if minute_counter % 60 == 0:
|
||||
try:
|
||||
# initialize the list of all extensions
|
||||
await InstallableExtension.get_installable_extensions(
|
||||
post_refresh_cache=True
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error(ex)
|
||||
|
||||
minute_counter += 1
|
||||
await asyncio.sleep(60)
|
||||
async def process_next_audit_entry() -> None:
|
||||
"""
|
||||
Waits for audit entries to be pushed to the queue.
|
||||
Then it inserts the entries into the DB.
|
||||
"""
|
||||
data = await audit_queue.get()
|
||||
await create_audit_entry(data)
|
||||
|
||||
|
||||
async def _notify_server_status() -> None:
|
||||
async def refresh_extension_cache() -> None:
|
||||
# only refreshes every 10 minutes
|
||||
await InstallableExtension.get_installable_extensions()
|
||||
|
||||
|
||||
async def notify_server_status() -> None:
|
||||
accounts = await get_accounts(filters=Filters(limit=0))
|
||||
wallets_count = await get_wallets_count()
|
||||
payments = await get_payments_status_count()
|
||||
|
||||
status = await get_balance_delta()
|
||||
values = {
|
||||
"up_time": settings.lnbits_server_up_time,
|
||||
@@ -89,98 +55,38 @@ async def _notify_server_status() -> None:
|
||||
enqueue_admin_notification(NotificationType.server_status, values)
|
||||
|
||||
|
||||
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue) -> None:
|
||||
"""
|
||||
This worker dispatches events to all extensions and dispatches webhooks.
|
||||
"""
|
||||
while settings.lnbits_running:
|
||||
payment = await invoice_paid_queue.get()
|
||||
logger.trace("received invoice paid event")
|
||||
# payment notification
|
||||
wallet = await get_wallet(payment.wallet_id)
|
||||
if wallet:
|
||||
await send_payment_notification(wallet, payment)
|
||||
|
||||
|
||||
async def wait_for_audit_data() -> None:
|
||||
"""
|
||||
Waits for audit entries to be pushed to the queue.
|
||||
Then it inserts the entries into the DB.
|
||||
"""
|
||||
while settings.lnbits_running:
|
||||
data = await audit_queue.get()
|
||||
try:
|
||||
await create_audit_entry(data)
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
await asyncio.sleep(3)
|
||||
|
||||
|
||||
async def wait_notification_messages() -> None:
|
||||
|
||||
while settings.lnbits_running:
|
||||
try:
|
||||
await process_next_notification()
|
||||
except Exception as ex:
|
||||
logger.warning("Payment notification error", ex)
|
||||
await asyncio.sleep(3)
|
||||
|
||||
|
||||
async def purge_audit_data() -> None:
|
||||
"""
|
||||
Remove audit entries which have passed their retention period.
|
||||
"""
|
||||
while settings.lnbits_running:
|
||||
try:
|
||||
await delete_expired_audit_entries()
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
|
||||
# clean every hour
|
||||
await asyncio.sleep(60 * 60)
|
||||
|
||||
|
||||
async def collect_exchange_rates_data() -> None:
|
||||
"""
|
||||
Collect exchange rates data. Used for monitoring only.
|
||||
"""
|
||||
while settings.lnbits_running:
|
||||
currency = settings.lnbits_default_accounting_currency or "USD"
|
||||
max_history_size = settings.lnbits_exchange_history_size
|
||||
sleep_time = settings.lnbits_exchange_history_refresh_interval_seconds
|
||||
|
||||
if sleep_time > 0:
|
||||
try:
|
||||
if (
|
||||
settings.lnbits_price_aggregator_enabled
|
||||
and settings.lnbits_price_aggregator_url
|
||||
):
|
||||
price = await btc_price_from_aggregator(currency)
|
||||
if price:
|
||||
cache.set(
|
||||
f"btc-price-{currency}",
|
||||
price,
|
||||
expiry=settings.lnbits_exchange_rate_cache_seconds,
|
||||
)
|
||||
settings.append_exchange_rate_datapoint(
|
||||
{"Aggregator": price}, max_history_size
|
||||
)
|
||||
else:
|
||||
rates = await btc_rates(currency)
|
||||
if rates:
|
||||
rates_values = [r[1] for r in rates]
|
||||
lnbits_rate = sum(rates_values) / len(rates_values)
|
||||
rates.append(("LNbits", lnbits_rate))
|
||||
cache.set(
|
||||
f"btc-price-{currency}",
|
||||
lnbits_rate,
|
||||
expiry=settings.lnbits_exchange_rate_cache_seconds,
|
||||
)
|
||||
settings.append_exchange_rate_datapoint(
|
||||
dict(rates), max_history_size
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
currency = settings.lnbits_default_accounting_currency or "USD"
|
||||
max_history_size = settings.lnbits_exchange_history_size
|
||||
try:
|
||||
if (
|
||||
settings.lnbits_price_aggregator_enabled
|
||||
and settings.lnbits_price_aggregator_url
|
||||
):
|
||||
price = await btc_price_from_aggregator(currency)
|
||||
if price:
|
||||
cache.set(
|
||||
f"btc-price-{currency}",
|
||||
price,
|
||||
expiry=settings.lnbits_exchange_rate_cache_seconds,
|
||||
)
|
||||
settings.append_exchange_rate_datapoint(
|
||||
{"Aggregator": price}, max_history_size
|
||||
)
|
||||
else:
|
||||
sleep_time = 60
|
||||
await asyncio.sleep(sleep_time)
|
||||
rates = await btc_rates(currency)
|
||||
if rates:
|
||||
rates_values = [r[1] for r in rates]
|
||||
lnbits_rate = sum(rates_values) / len(rates_values)
|
||||
rates.append(("LNbits", lnbits_rate))
|
||||
cache.set(
|
||||
f"btc-price-{currency}",
|
||||
lnbits_rate,
|
||||
expiry=settings.lnbits_exchange_rate_cache_seconds,
|
||||
)
|
||||
settings.append_exchange_rate_datapoint(dict(rates), max_history_size)
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
|
||||
@@ -20,7 +20,7 @@ from lnbits.core.services.settings import dict_to_settings
|
||||
from lnbits.decorators import check_admin, check_super_user
|
||||
from lnbits.server import server_restart
|
||||
from lnbits.settings import AdminSettings, Settings, UpdateSettings, settings
|
||||
from lnbits.tasks import invoice_listeners
|
||||
from lnbits.task_manager import PublicTask, task_manager
|
||||
|
||||
from .. import core_app_extra
|
||||
from ..crud import get_admin_settings, reset_core_settings, update_admin_settings
|
||||
@@ -44,11 +44,10 @@ async def api_auditor():
|
||||
name="Monitor",
|
||||
description="show the current listeners and other monitoring data",
|
||||
dependencies=[Depends(check_admin)],
|
||||
response_model=list[PublicTask],
|
||||
)
|
||||
async def api_monitor():
|
||||
return {
|
||||
"invoice_listeners": list(invoice_listeners.keys()),
|
||||
}
|
||||
async def api_monitor() -> list[PublicTask]:
|
||||
return task_manager.get_public_tasks()
|
||||
|
||||
|
||||
@admin_router.get(
|
||||
|
||||
Reference in New Issue
Block a user