@ -29,7 +29,7 @@ import time
import traceback
import sys
import threading
from typing import Dict , Optional , Tuple
from typing import Dict , Optional , Tuple , Iterable
from base64 import b64decode
from collections import defaultdict
@ -39,6 +39,7 @@ import jsonrpcclient
import jsonrpcserver
from jsonrpcserver import response
from jsonrpcclient . clients . aiohttp_client import AiohttpClient
from aiorpcx import TaskGroup
from . network import Network
from . util import ( json_decode , to_bytes , to_string , profiler , standardize_path , constant_time_compare )
@ -280,28 +281,44 @@ class Daemon(Logger):
if fd is None :
raise Exception ( ' failed to lock daemon; already running? ' )
self . asyncio_loop = asyncio . get_event_loop ( )
if config . get ( ' offline ' ) :
self . network = None
els e :
self . network = Network ( config )
if not config . g et ( ' offlin e' ) :
self . network = Network ( config , daemon = self )
self . fx = FxThread ( config , self . network )
self . gui_object = None
# path -> wallet; make sure path is standardized.
self . _wallets = { } # type: Dict[str, Abstract_Wallet]
jobs = [ self . fx . run ]
daemon_ jobs = [ ]
# Setup JSONRPC server
if listen_jsonrpc :
jobs . append ( self . start_jsonrpc ( config , fd ) )
daemon_ jobs. append ( self . start_jsonrpc ( config , fd ) )
# request server
if self . config . get ( ' run_payserver ' ) :
self . pay_server = None
if not config . get ( ' offline ' ) and self . config . get ( ' run_payserver ' ) :
self . pay_server = PayServer ( self )
jobs . append ( self . pay_server . run ( ) )
daemon_ jobs. append ( self . pay_server . run ( ) )
# server-side watchtower
if self . config . get ( ' run_watchtower ' ) :
self . watchtower = None
if not config . get ( ' offline ' ) and self . config . get ( ' run_watchtower ' ) :
self . watchtower = WatchTowerServer ( self . network )
jobs . append ( self . watchtower . run )
daemon_ jobs. append ( self . watchtower . run )
if self . network :
self . network . start ( jobs )
self . network . start ( jobs = [ self . fx . run ] )
self . taskgroup = TaskGroup ( )
asyncio . run_coroutine_threadsafe ( self . _run ( jobs = daemon_jobs ) , self . asyncio_loop )
@log_exceptions
async def _run ( self , jobs : Iterable = None ) :
if jobs is None :
jobs = [ ]
try :
async with self . taskgroup as group :
[ await group . spawn ( job ) for job in jobs ]
except BaseException as e :
self . logger . exception ( ' daemon.taskgroup died. ' )
finally :
self . logger . info ( " stopping daemon.taskgroup " )
async def authenticate ( self , headers ) :
if self . rpc_password == ' ' :
@ -462,7 +479,7 @@ class Daemon(Logger):
def is_running ( self ) :
with self . running_lock :
return self . running
return self . running and not self . taskgroup . closed ( )
def stop ( self ) :
with self . running_lock :
@ -477,8 +494,15 @@ class Daemon(Logger):
if self . network :
self . logger . info ( " shutting down network " )
self . network . stop ( )
self . logger . info ( " stopping, removing lockfile " )
self . logger . info ( " stopping taskgroup " )
fut = asyncio . run_coroutine_threadsafe ( self . taskgroup . cancel_remaining ( ) , self . asyncio_loop )
try :
fut . result ( timeout = 2 )
except ( asyncio . TimeoutError , asyncio . CancelledError ) :
pass
self . logger . info ( " removing lockfile " )
remove_lockfile ( get_lockfile ( self . config ) )
self . logger . info ( " stopped " )
def run_gui ( self , config , plugins ) :
threading . current_thread ( ) . setName ( ' GUI ' )