diff --git a/Furious/Backends/ExternalCore/Plugin.py b/Furious/Backends/ExternalCore/Plugin.py index 1437a4d3..56a95900 100644 --- a/Furious/Backends/ExternalCore/Plugin.py +++ b/Furious/Backends/ExternalCore/Plugin.py @@ -15,15 +15,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Integrate user-managed local executables with the Furious kernel API.""" +"""Integrate user-managed local executables with the core-runtime API.""" from __future__ import annotations from Furious.Plugins.API import ( FuriousPlugin, - KernelFactory, - KernelLaunch, - KernelRequest, + CoreRuntimeFactory, + CoreRuntimeLaunch, + CoreRuntimeRequest, PluginMetadata, ) @@ -39,20 +39,20 @@ __all__ = ['ExternalCorePlugin'] logger = logging.getLogger(__name__) -class ExternalCoreKernelFactory(KernelFactory): +class ExternalCoreRuntimeFactory(CoreRuntimeFactory): """Construct a managed direct-process runtime from a structured profile.""" factoryId = 'official.external-core' configurationTypes = (ConfigExternalCore,) - kernelTypes = (ExternalCoreProcess,) + runtimeTypes = (ExternalCoreProcess,) def usesApplicationTun2socks(self, config) -> bool: """Honor this profile's explicit host-managed tun2socks preference.""" return config.usesApplicationTun2socks() - def create(self, request: KernelRequest): + def create(self, request: CoreRuntimeRequest): """Create an External Core process launch for the connection manager.""" - process = ExternalCoreProcess( + runtime = ExternalCoreProcess( exitCallback=request.exitCallback, msgCallback=request.messageCallback, ) @@ -72,8 +72,8 @@ class ExternalCoreKernelFactory(KernelFactory): 'application-managed TUN will be skipped' ) - return KernelLaunch( - process, + return CoreRuntimeLaunch( + runtime, request.configuration, options=request.options, ) @@ -98,5 +98,5 @@ class ExternalCorePlugin(FuriousPlugin): self.capabilities = ( *EXTERNAL_CORE_PROTOCOL_HANDLERS, *EXTERNAL_CORE_PROTOCOL_EDITORS, - ExternalCoreKernelFactory(), + ExternalCoreRuntimeFactory(), ) diff --git a/Furious/Backends/ExternalCore/Process.py b/Furious/Backends/ExternalCore/Process.py index 4104259e..f0190325 100644 --- a/Furious/Backends/ExternalCore/Process.py +++ b/Furious/Backends/ExternalCore/Process.py @@ -20,7 +20,7 @@ from __future__ import annotations from Furious.Core import CoreProcessState -from Furious.Interface import RuntimeKernel +from Furious.Interface import CoreRuntime from .Configuration import ConfigExternalCore @@ -38,7 +38,7 @@ __all__ = ['ExternalCoreProcess'] logger = logging.getLogger(__name__) -class ExternalCoreProcess(RuntimeKernel): +class ExternalCoreProcess(CoreRuntime): """Manage one direct external process and its output-reader threads.""" StartupObservationTimeout = 0.25 diff --git a/Furious/Backends/Hysteria1/Plugin.py b/Furious/Backends/Hysteria1/Plugin.py index 8d3903bb..17a368dd 100644 --- a/Furious/Backends/Hysteria1/Plugin.py +++ b/Furious/Backends/Hysteria1/Plugin.py @@ -34,12 +34,12 @@ __all__ = ['Hysteria1Plugin'] logger = logging.getLogger(__name__) -class Hysteria1KernelFactory(KernelFactory): - """Construct Hysteria 1 kernels independently of protocol handling.""" +class Hysteria1CoreRuntimeFactory(CoreRuntimeFactory): + """Construct Hysteria 1 core runtimes independently of protocol handling.""" factoryId = 'official.hysteria1' configurationTypes = (ConfigHysteria1,) - kernelTypes = (Hysteria1,) + runtimeTypes = (Hysteria1,) def routingOptions(self, config=None): """Return the routing modes supported by Hysteria 1.""" @@ -48,8 +48,8 @@ class Hysteria1KernelFactory(KernelFactory): for routing in AppBuiltinRouting ) - def create(self, request: KernelRequest): - """Configure routing and create a Hysteria 1 kernel launch.""" + def create(self, request: CoreRuntimeRequest): + """Configure routing and create a Hysteria 1 core-runtime launch.""" config, routing = ( request.configuration, request.routing, @@ -81,13 +81,13 @@ class Hysteria1KernelFactory(KernelFactory): logger.info(f'routing is {routing}') logger.info(f'RoutingObject: {routingObject}') - process = Hysteria1( + runtime = Hysteria1( exitCallback=request.exitCallback, msgCallback=request.messageCallback, ) - return KernelLaunch( - process, + return CoreRuntimeLaunch( + runtime, config, arguments=( Hysteria1.rule(routingObject.get('rule', '')), @@ -135,5 +135,5 @@ class Hysteria1Plugin(FuriousPlugin): self.capabilities = ( *HYSTERIA1_PROTOCOL_HANDLERS, *HYSTERIA1_PROTOCOL_EDITORS, - Hysteria1KernelFactory(), + Hysteria1CoreRuntimeFactory(), ) diff --git a/Furious/Backends/Hysteria2/Plugin.py b/Furious/Backends/Hysteria2/Plugin.py index a3925366..2ecf13f9 100644 --- a/Furious/Backends/Hysteria2/Plugin.py +++ b/Furious/Backends/Hysteria2/Plugin.py @@ -153,12 +153,12 @@ class Hysteria2ActionProvider(ActionProvider): ) -class Hysteria2KernelFactory(KernelFactory): - """Construct Hysteria 2 kernels independently of protocol handling.""" +class Hysteria2CoreRuntimeFactory(CoreRuntimeFactory): + """Construct Hysteria 2 core runtimes independently of protocol handling.""" factoryId = 'official.hysteria2' configurationTypes = (ConfigHysteria2,) - kernelTypes = (Hysteria2,) + runtimeTypes = (Hysteria2,) def prepareTUN(self, config) -> bool: """Preserve user TUN or replace it with Furious-managed native TUN.""" @@ -195,20 +195,20 @@ class Hysteria2KernelFactory(KernelFactory): """Use host tun2socks only when the runtime has no native TUN block.""" return not hasHysteria2TUNConfig(config) - def create(self, request: KernelRequest): - """Create a prepared Hysteria 2 kernel launch.""" + def create(self, request: CoreRuntimeRequest): + """Create a prepared Hysteria 2 core-runtime launch.""" if request.log: logger.info(f'core {Hysteria2.name()} configured') - process = Hysteria2( + runtime = Hysteria2( exitCallback=request.exitCallback, msgCallback=request.messageCallback, ) - setattr(process, 'hysteria2StatsTarget', configuredHysteria2StatsTarget()) + setattr(runtime, 'hysteria2StatsTarget', configuredHysteria2StatsTarget()) - return KernelLaunch( - process, + return CoreRuntimeLaunch( + runtime, request.configuration, options=request.options, ) @@ -261,7 +261,7 @@ class Hysteria2Plugin(FuriousPlugin): self.capabilities = ( *HYSTERIA2_PROTOCOL_HANDLERS, *HYSTERIA2_PROTOCOL_EDITORS, - Hysteria2KernelFactory(), + Hysteria2CoreRuntimeFactory(), Hysteria2StatsProvider(), Hysteria2SettingsProvider(), Hysteria2ActionProvider(), diff --git a/Furious/Backends/Hysteria2/Stats.py b/Furious/Backends/Hysteria2/Stats.py index 02417931..869d921d 100644 --- a/Furious/Backends/Hysteria2/Stats.py +++ b/Furious/Backends/Hysteria2/Stats.py @@ -196,11 +196,11 @@ class Hysteria2StatsProvider(TrafficStatsProvider): """Expose Hysteria server counters through the shared metrics capability.""" providerId = 'official.hysteria2.stats' - kernelTypes = (Hysteria2,) + runtimeTypes = (Hysteria2,) - def monitorForKernel(self, kernel) -> Optional[TrafficStatsMonitor]: - """Return a monitor when the active kernel has a configured API target.""" - target = getattr(kernel, 'hysteria2StatsTarget', None) + def monitorForRuntime(self, runtime) -> Optional[TrafficStatsMonitor]: + """Return a monitor when the active runtime has a configured API target.""" + target = getattr(runtime, 'hysteria2StatsTarget', None) if not isinstance(target, Hysteria2StatsTarget): return None diff --git a/Furious/Backends/Xray/Plugin.py b/Furious/Backends/Xray/Plugin.py index 04b4ad8f..f776d36b 100644 --- a/Furious/Backends/Xray/Plugin.py +++ b/Furious/Backends/Xray/Plugin.py @@ -152,12 +152,12 @@ class XrayActionProvider(ActionProvider): ) -class XrayKernelFactory(KernelFactory): - """Construct Xray kernels independently of protocols and editors.""" +class XrayCoreRuntimeFactory(CoreRuntimeFactory): + """Construct Xray core runtimes independently of protocols and editors.""" factoryId = 'official.xray' configurationTypes = (ConfigXray,) - kernelTypes = (XrayCore,) + runtimeTypes = (XrayCore,) def fromMapping(self, configuration, **kwargs): """Recognize a complete Xray configuration without a proxy profile.""" @@ -237,8 +237,8 @@ class XrayKernelFactory(KernelFactory): """Point Xray-core at Furious's bundled geo-asset directory.""" os.environ['XRAY_LOCATION_ASSET'] = str(XRAY_ASSET_DIR) - def create(self, request: KernelRequest): - """Configure routing and create an Xray-core launch.""" + def create(self, request: CoreRuntimeRequest): + """Configure routing and create an Xray core-runtime launch.""" config, routing, proxyModeOnly, log = ( request.configuration, request.routing, @@ -321,13 +321,13 @@ class XrayKernelFactory(KernelFactory): statsTarget = None - process = XrayCore( + runtime = XrayCore( exitCallback=request.exitCallback, msgCallback=request.messageCallback, ) - process.xrayStatsTarget = statsTarget + runtime.xrayStatsTarget = statsTarget - return KernelLaunch(process, config, options=request.options) + return CoreRuntimeLaunch(runtime, config, options=request.options) def prepareDownloadTest(self, config, port: int): """Create an Xray configuration with one local HTTP test inbound.""" @@ -426,7 +426,7 @@ class XrayPlugin(FuriousPlugin): self.capabilities = ( *XRAY_PROTOCOL_HANDLERS, *XRAY_PROTOCOL_EDITORS, - XrayKernelFactory(), + XrayCoreRuntimeFactory(), XrayStatsProvider(), XrayActionProvider(), ) diff --git a/Furious/Backends/Xray/Stats.py b/Furious/Backends/Xray/Stats.py index 00446fbc..ad37c99b 100644 --- a/Furious/Backends/Xray/Stats.py +++ b/Furious/Backends/Xray/Stats.py @@ -271,11 +271,11 @@ class XrayStatsProvider(TrafficStatsProvider): """Expose active Xray outbound traffic through the plugin capability API.""" providerId = 'official.xray.stats' - kernelTypes = (XrayCore,) + runtimeTypes = (XrayCore,) - def monitorForKernel(self, kernel) -> Optional[TrafficStatsMonitor]: + def monitorForRuntime(self, runtime) -> Optional[TrafficStatsMonitor]: """Return a monitor when the active core has a valid outbound target.""" - target = getattr(kernel, 'xrayStatsTarget', None) + target = getattr(runtime, 'xrayStatsTarget', None) if not isinstance(target, XrayStatsTarget): return None diff --git a/Furious/Controllers/ConnectionController.py b/Furious/Controllers/ConnectionController.py index b942158b..a729e907 100644 --- a/Furious/Controllers/ConnectionController.py +++ b/Furious/Controllers/ConnectionController.py @@ -86,7 +86,7 @@ class ConnectionController(QtCore.QObject): stateChanged = QtCore.Signal(object) activeConfigurationChanged = QtCore.Signal(object) interactionEnabledChanged = QtCore.Signal(bool) - processesChanged = QtCore.Signal(object) + runtimesChanged = QtCore.Signal(object) progressStarted = QtCore.Signal() progressFinished = QtCore.Signal(bool) notificationRequested = QtCore.Signal(str) @@ -117,9 +117,9 @@ class ConnectionController(QtCore.QObject): return self._activeConfiguration @property - def processes(self): - """Return an immutable snapshot of managed core processes.""" - return tuple(self._coreManager.processesPool) + def runtimes(self): + """Return an immutable snapshot of managed core runtimes.""" + return tuple(self._coreManager.runtimes) @property def lastError(self): @@ -167,9 +167,9 @@ class ConnectionController(QtCore.QObject): self._activeConfiguration = configuration self.activeConfigurationChanged.emit(configuration) - def _setProcessesChanged(self): - """Publish a stable process snapshot after runtime changes.""" - self.processesChanged.emit(self.processes) + def _emitRuntimesChanged(self): + """Publish a stable snapshot after managed runtimes change.""" + self.runtimesChanged.emit(self.runtimes) def _reportError(self, message: str, details: str = ''): """Publish a user-facing error without choosing its presentation.""" @@ -296,7 +296,7 @@ class ConnectionController(QtCore.QObject): success = False startExceptionDetails = str(ex) - self._setProcessesChanged() + self._emitRuntimesChanged() if not self._actionQueue.empty(): while not self._actionQueue.empty(): @@ -372,7 +372,7 @@ class ConnectionController(QtCore.QObject): # strand every connection UI in the disabled Disconnecting state. logger.error(f'failed to stop connection runtime: {ex}') - self._setProcessesChanged() + self._emitRuntimesChanged() self._reset() while not self._actionQueue.empty(): @@ -463,7 +463,7 @@ class ConnectionController(QtCore.QObject): if callable(action): action() - def coreExitCallback(self, core: CoreProcess, exitcode: int): + def coreExitCallback(self, core: CoreRuntime, exitcode: int): """Translate a core exit into a queued lifecycle operation.""" def putItem(item): @@ -475,12 +475,12 @@ class ConnectionController(QtCore.QObject): pass - if exitcode == CoreProcess.ExitCode.SystemShuttingDown.value: + if exitcode == CoreRuntime.ExitCode.SystemShuttingDown.value: return None - if exitcode == CoreProcess.ExitCode.ConfigurationError.value: + if exitcode == CoreRuntime.ExitCode.ConfigurationError.value: message = f'{core.name()}: ' + _('Invalid server configuration') - elif exitcode == CoreProcess.ExitCode.ServerStartFailure.value: + elif exitcode == CoreRuntime.ExitCode.ServerStartFailure.value: message = f'{core.name()}: ' + _('Failed to start core') else: pluginMessage = getPluginRegistry().coreExitMessage(core, exitcode) diff --git a/Furious/Core/CoreProcessWorker.py b/Furious/Core/CoreProcessWorker.py index 710c4afe..5c120b83 100644 --- a/Furious/Core/CoreProcessWorker.py +++ b/Furious/Core/CoreProcessWorker.py @@ -226,7 +226,7 @@ class MsgQueue(multiprocessing.queues.Queue): self.startTimer() -class CoreProcessMonitor(CoreProcess, ABC): +class CoreProcessMonitor(CoreRuntime, ABC): """Track the state and lifetime of a proxy-core child process.""" StopJoinTimeout = 3 @@ -312,7 +312,7 @@ class CoreProcessMonitor(CoreProcess, ABC): self.process = None def dispose(self): - """Release the monitor timer after this kernel leaves its owner pool.""" + """Release the monitor timer after this runtime leaves its owner pool.""" self.daemon.stop() try: diff --git a/Furious/Interface/Process.py b/Furious/Interface/Runtime.py similarity index 79% rename from Furious/Interface/Process.py rename to Furious/Interface/Runtime.py index e345d4d3..c0bbe3ce 100644 --- a/Furious/Interface/Process.py +++ b/Furious/Interface/Runtime.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Define the common proxy-core process interface.""" +"""Define the lifecycle contract for one managed proxy-core runtime.""" from __future__ import annotations @@ -26,21 +26,27 @@ from abc import ABC, abstractmethod from enum import Enum from typing import Callable, Union -import logging import functools +import logging -__all__ = ['CoreProcess', 'RuntimeKernel'] +__all__ = ['CoreRuntime'] logger = logging.getLogger(__name__) _INVALID_CONFIGURATION_ERROR = 'Invalid server configuration' -class RuntimeKernel(ABC): - """Define the lifecycle shared by plugin-created runtime kernels.""" +class CoreRuntime(ABC): + """Manage one startable and stoppable proxy-core runtime instance. + + A runtime represents the semantic lifecycle of one proxy core. Concrete + implementations may use a subprocess, multiprocessing, or an in-process + binding; those execution mechanisms are intentionally outside this + contract. + """ class ExitCode(Enum): - """Enumerate process exit codes.""" + """Enumerate shared semantic core exit codes.""" ConfigurationError = 23 # Windows: 4294967295. Darwin, Linux: 255 (-1) @@ -50,16 +56,16 @@ class RuntimeKernel(ABC): def __init__( self, - exitCallback: Union[Callable[[RuntimeKernel, int], None], None] = None, + exitCallback: Union[Callable[[CoreRuntime, int], None], None] = None, ): - """Initialize the core process.""" + """Initialize an idle core runtime.""" super().__init__() self._exitCallback = exitCallback self._startError = '' def callExitCallback(self, exitcode: int): - """Call exit callback.""" + """Report this runtime's termination to its lifecycle owner.""" if callable(self._exitCallback): self._exitCallback(self, exitcode) @@ -77,7 +83,7 @@ class RuntimeKernel(ABC): @functools.singledispatchmethod def toJSONString(self, config, **kwargs) -> str: - """Serialize the configuration as JSON text.""" + """Serialize a prepared runtime configuration as JSON text.""" self.setStartError(_INVALID_CONFIGURATION_ERROR) logger.error( @@ -89,7 +95,7 @@ class RuntimeKernel(ABC): @toJSONString.register(str) def _(self, config, **kwargs) -> str: - """Handle the registered singledispatch variant.""" + """Accept an already serialized runtime configuration.""" if not config: self.setStartError(_INVALID_CONFIGURATION_ERROR) @@ -103,7 +109,7 @@ class RuntimeKernel(ABC): @toJSONString.register(dict) def _(self, config, **kwargs) -> str: - """Handle the registered singledispatch variant.""" + """Serialize a mapping with its own serializer or the shared encoder.""" serializer = getattr(config, 'toJSONString', None) try: @@ -142,25 +148,21 @@ class RuntimeKernel(ABC): @staticmethod @abstractmethod def name() -> str: - """Return the process implementation name.""" + """Return the proxy-core implementation name.""" raise NotImplementedError @staticmethod @abstractmethod def version() -> str: - """Return the bundled core version.""" + """Return the bundled core version, if one exists.""" raise NotImplementedError @abstractmethod def start(self, *args, **kwargs) -> bool: - """Start the runtime kernel.""" + """Start this core runtime.""" raise NotImplementedError @abstractmethod def stop(self): - """Stop the runtime kernel.""" + """Stop this core runtime.""" raise NotImplementedError - - -# Compatibility name retained for existing process implementations. -CoreProcess = RuntimeKernel diff --git a/Furious/Interface/__init__.py b/Furious/Interface/__init__.py index d80f04d4..2e8298f2 100644 --- a/Furious/Interface/__init__.py +++ b/Furious/Interface/__init__.py @@ -21,14 +21,13 @@ from __future__ import annotations from .Application import ApplicationRunner from .Editor import EditorBinding, EditorWidgetBinding -from .Process import CoreProcess, RuntimeKernel +from .Runtime import CoreRuntime from .Storage import StorageBackend __all__ = [ 'ApplicationRunner', - 'CoreProcess', + 'CoreRuntime', 'EditorBinding', 'EditorWidgetBinding', 'StorageBackend', - 'RuntimeKernel', ] diff --git a/Furious/Plugins/API.py b/Furious/Plugins/API.py index bfe0ef6a..0095d863 100644 --- a/Furious/Plugins/API.py +++ b/Furious/Plugins/API.py @@ -27,10 +27,10 @@ __all__ = [ 'PLUGIN_API_VERSION', 'ActionProvider', 'CapabilityKind', + 'CoreRuntimeFactory', + 'CoreRuntimeLaunch', + 'CoreRuntimeRequest', 'FuriousPlugin', - 'KernelFactory', - 'KernelLaunch', - 'KernelRequest', 'NavigationPageDescriptor', 'NavigationPageProvider', 'PluginCapability', @@ -68,7 +68,7 @@ class CapabilityKind(str, Enum): Protocol = 'protocol' ProtocolEditor = 'protocol-editor' SubscriptionDecoder = 'subscription-decoder' - KernelFactory = 'kernel-factory' + CoreRuntimeFactory = 'core-runtime-factory' TrafficStats = 'traffic-stats' PluginSettings = 'plugin-settings' NavigationPage = 'navigation-page' @@ -371,25 +371,25 @@ class TrafficStatsMonitor: class TrafficStatsProvider(PluginCapability): - """Provide traffic counters for one or more runtime kernel types.""" + """Provide traffic counters for one or more core-runtime types.""" capabilityKind = CapabilityKind.TrafficStats providerId = '' - kernelTypes = tuple() + runtimeTypes = tuple() @property def capabilityId(self) -> str: """Return the traffic-statistics provider identifier.""" return self.providerId - def monitorForKernel(self, kernel) -> Optional[TrafficStatsMonitor]: - """Return a monitor for *kernel* or ``None`` when unavailable.""" + def monitorForRuntime(self, runtime) -> Optional[TrafficStatsMonitor]: + """Return a monitor for *runtime* or ``None`` when unavailable.""" return None @dataclass(frozen=True) -class KernelRequest: - """Describe one runtime-kernel construction request.""" +class CoreRuntimeRequest: + """Describe one core-runtime construction request.""" configuration: Any routing: str @@ -401,18 +401,18 @@ class KernelRequest: @dataclass(frozen=True) -class KernelLaunch: - """Bind a constructed kernel to its prepared start arguments.""" +class CoreRuntimeLaunch: + """Bind a constructed core runtime to its prepared start arguments.""" - kernel: Any + runtime: Any configuration: Any arguments: Tuple[Any, ...] = tuple() options: Mapping[str, Any] = field(default_factory=dict) def start(self) -> bool: - """Start the prepared kernel.""" + """Start the prepared core runtime.""" return bool( - self.kernel.start( + self.runtime.start( self.configuration, *self.arguments, **dict(self.options), @@ -420,13 +420,13 @@ class KernelLaunch: ) -class KernelFactory(PluginCapability): - """Construct runtime kernels independently from protocol handling.""" +class CoreRuntimeFactory(PluginCapability): + """Construct managed core runtimes independently from protocol handling.""" - capabilityKind = CapabilityKind.KernelFactory + capabilityKind = CapabilityKind.CoreRuntimeFactory factoryId = '' configurationTypes = tuple() - kernelTypes = tuple() + runtimeTypes = tuple() @property def capabilityId(self) -> str: @@ -457,10 +457,10 @@ class KernelFactory(PluginCapability): return tuple() def configureEnvironment(self): - """Set optional environment required by this backend's process.""" + """Set optional environment required by this backend's runtime.""" - def create(self, request: KernelRequest) -> Optional[KernelLaunch]: - """Create a prepared runtime kernel launch.""" + def create(self, request: CoreRuntimeRequest) -> Optional[CoreRuntimeLaunch]: + """Create a prepared core-runtime launch.""" return None def prepareDownloadTest(self, config, port: int): diff --git a/Furious/Plugins/Registry.py b/Furious/Plugins/Registry.py index f75f3a46..f7f2a208 100644 --- a/Furious/Plugins/Registry.py +++ b/Furious/Plugins/Registry.py @@ -53,6 +53,11 @@ def _normalizeScheme(value) -> str: return str(value).strip().rstrip(':').casefold() +def _runtimeTypes(capability) -> tuple: + """Return the capability's declared managed runtime types.""" + return tuple(getattr(capability, 'runtimeTypes', tuple()) or tuple()) + + def _schemeFromURI(uri: str) -> str: """Extract a normalized scheme from *uri*.""" try: @@ -84,7 +89,7 @@ class PluginRegistry: self._protocolEditors = {} self._factories = {} self._configurationFactories = {} - self._kernelFactories = {} + self._runtimeFactories = {} self._trafficStatsProviders = {} self._decoders = {} self._initializedPlugins = [] @@ -150,8 +155,8 @@ class PluginRegistry: localSchemes = set() localEditorProtocols = set() localConfigurationTypes = [] - localKernelTypes = [] - localTrafficStatsKernelTypes = [] + localRuntimeTypes = [] + localTrafficStatsRuntimeTypes = [] entries = [] for capability in capabilities: @@ -244,13 +249,13 @@ class PluginRegistry: localEditorProtocols.update(protocolIds) detail = protocolIds - elif isinstance(capability, KernelFactory): + elif isinstance(capability, CoreRuntimeFactory): configurationTypes = tuple(capability.configurationTypes) - kernelTypes = tuple(capability.kernelTypes) + runtimeTypes = _runtimeTypes(capability) if not configurationTypes: raise ValueError( - f'kernel factory {capability.factoryId!r} must declare ' + f'core runtime factory {capability.factoryId!r} must declare ' f'configuration types' ) @@ -262,16 +267,16 @@ class PluginRegistry: localConfigurationTypes, ), ( - kernelTypes, - 'kernel', - tuple(self._kernelFactories), - localKernelTypes, + runtimeTypes, + 'runtime', + tuple(self._runtimeFactories), + localRuntimeTypes, ), ): for itemType in values: if not isinstance(itemType, type): raise TypeError( - f'kernel factory {label} types must be classes' + f'core runtime factory {label} types must be classes' ) if any( @@ -286,38 +291,38 @@ class PluginRegistry: local.append(itemType) - detail = (configurationTypes, kernelTypes) + detail = (configurationTypes, runtimeTypes) elif isinstance(capability, TrafficStatsProvider): - kernelTypes = tuple(capability.kernelTypes) + runtimeTypes = _runtimeTypes(capability) - if not kernelTypes: + if not runtimeTypes: raise ValueError( f'traffic stats provider {capability.providerId!r} must ' - f'declare kernel types' + f'declare runtime types' ) - for kernelType in kernelTypes: - if not isinstance(kernelType, type): + for runtimeType in runtimeTypes: + if not isinstance(runtimeType, type): raise TypeError( - 'traffic stats provider kernel types must be classes' + 'traffic stats provider runtime types must be classes' ) if any( - issubclass(kernelType, registeredType) - or issubclass(registeredType, kernelType) + issubclass(runtimeType, registeredType) + or issubclass(registeredType, runtimeType) for registeredType in ( *self._trafficStatsProviders, - *localTrafficStatsKernelTypes, + *localTrafficStatsRuntimeTypes, ) ): raise ValueError( - f'traffic stats kernel type ' - f'{kernelType.__name__!r} overlaps a registered type' + f'traffic stats runtime type ' + f'{runtimeType.__name__!r} overlaps a registered type' ) - localTrafficStatsKernelTypes.append(kernelType) + localTrafficStatsRuntimeTypes.append(runtimeType) - detail = kernelTypes + detail = runtimeTypes elif isinstance(capability, SubscriptionDecoder): if not isinstance(capability.priority, int): raise TypeError('subscription decoder priority must be an integer') @@ -356,17 +361,17 @@ class PluginRegistry: for protocolId in detail: self._protocolEditors[protocolId] = entry - elif isinstance(capability, KernelFactory): - configurationTypes, kernelTypes = detail + elif isinstance(capability, CoreRuntimeFactory): + configurationTypes, runtimeTypes = detail self._factories[capabilityId] = entry for configType in configurationTypes: self._configurationFactories[configType] = entry - for kernelType in kernelTypes: - self._kernelFactories[kernelType] = entry + for runtimeType in runtimeTypes: + self._runtimeFactories[runtimeType] = entry elif isinstance(capability, TrafficStatsProvider): - for kernelType in detail: - self._trafficStatsProviders[kernelType] = entry + for runtimeType in detail: + self._trafficStatsProviders[runtimeType] = entry elif isinstance(capability, SubscriptionDecoder): self._decoders[capabilityId] = entry @@ -421,7 +426,7 @@ class PluginRegistry: '_protocolEditors', '_factories', '_configurationFactories', - '_kernelFactories', + '_runtimeFactories', '_trafficStatsProviders', '_decoders', ): @@ -500,9 +505,9 @@ class PluginRegistry: """Return registered protocol editor providers.""" return self.capabilities(CapabilityKind.ProtocolEditor) - def kernelFactories(self): - """Return registered runtime kernel factories.""" - return self.capabilities(CapabilityKind.KernelFactory) + def coreRuntimeFactories(self): + """Return registered core-runtime factories.""" + return self.capabilities(CapabilityKind.CoreRuntimeFactory) def subscriptionDecoders(self): """Return subscription decoders in auto-detection priority order.""" @@ -571,32 +576,32 @@ class PluginRegistry: return None - def factoryForKernel(self, kernel): - """Return the runtime factory that owns *kernel*.""" - for kernelType, (_plugin, factory) in self._kernelFactories.items(): - if isinstance(kernel, kernelType): + def runtimeFactoryFor(self, runtime): + """Return the factory that owns *runtime*.""" + for runtimeType, (_plugin, factory) in self._runtimeFactories.items(): + if isinstance(runtime, runtimeType): return factory return None - def trafficStatsProviderForKernel(self, kernel): - """Return the traffic-statistics provider that owns *kernel*.""" - for kernelType, (_plugin, provider) in self._trafficStatsProviders.items(): - if isinstance(kernel, kernelType): + def trafficStatsProviderForRuntime(self, runtime): + """Return the traffic-statistics provider that owns *runtime*.""" + for runtimeType, (_plugin, provider) in self._trafficStatsProviders.items(): + if isinstance(runtime, runtimeType): return provider return None - def trafficStatsMonitorForKernels(self, kernels): - """Return the first available monitor for the active runtime kernels.""" - for kernel in kernels: - provider = self.trafficStatsProviderForKernel(kernel) + def trafficStatsMonitorForRuntimes(self, runtimes): + """Return the first monitor available for the active core runtimes.""" + for runtime in runtimes: + provider = self.trafficStatsProviderForRuntime(runtime) if provider is None: continue try: - monitor = provider.monitorForKernel(kernel) + monitor = provider.monitorForRuntime(runtime) except Exception as ex: # Any non-exit exceptions @@ -652,10 +657,10 @@ class PluginRegistry: else None ) - def pluginForKernel(self, kernel): - """Return the plugin that contributes a kernel's factory.""" - for kernelType, (plugin, _factory) in self._kernelFactories.items(): - if isinstance(kernel, kernelType): + def pluginForRuntime(self, runtime): + """Return the plugin that contributes a runtime's factory.""" + for runtimeType, (plugin, _factory) in self._runtimeFactories.items(): + if isinstance(runtime, runtimeType): return plugin return None @@ -771,7 +776,7 @@ class PluginRegistry: if result is not None: if not isinstance(result, factory.configurationTypes): logger.error( - f'kernel factory {factory.factoryId!r} returned an ' + f'core runtime factory {factory.factoryId!r} returned an ' f'unowned mapping result' ) continue @@ -780,7 +785,9 @@ class PluginRegistry: if len(factoryMatches) > 1: names = ', '.join(repr(item[0].factoryId) for item in factoryMatches) - raise ValueError(f'kernel configuration mapping is ambiguous: {names}') + raise ValueError( + f'core runtime configuration mapping is ambiguous: {names}' + ) return factoryMatches[0][1] if factoryMatches else None @@ -901,7 +908,7 @@ class PluginRegistry: handled = factory.prepareTUN(_connectionOf(config)) if not isinstance(handled, bool): - raise TypeError('kernel TUN preparation result must be a boolean') + raise TypeError('core runtime TUN preparation result must be a boolean') return handled except TUNPreparationError: @@ -926,7 +933,9 @@ class PluginRegistry: enabled = factory.usesApplicationTun2socks(_connectionOf(config)) if not isinstance(enabled, bool): - raise TypeError('kernel application tun2socks result must be a boolean') + raise TypeError( + 'core runtime application tun2socks result must be a boolean' + ) return enabled except Exception as ex: @@ -953,7 +962,7 @@ class PluginRegistry: for option in options: if not isinstance(option, RoutingOption): raise TypeError( - 'kernel routing options must be RoutingOption values' + 'core runtime routing options must be RoutingOption values' ) if not isinstance(option.id, str) or not option.id.strip(): @@ -993,14 +1002,14 @@ class PluginRegistry: return routing if routing in optionIds else optionIds[0] - def createKernel(self, config, routing, **kwargs): - """Create a prepared kernel launch for *config*.""" + def createCoreRuntime(self, config, routing, **kwargs): + """Create a prepared core-runtime launch for *config*.""" factory = self.factoryForConfig(config) if factory is None: return None - request = KernelRequest( + request = CoreRuntimeRequest( configuration=_connectionOf(config), routing=self.normalizeRouting(config, routing), exitCallback=kwargs.pop('exitCallback', None), @@ -1014,35 +1023,42 @@ class PluginRegistry: if launch is None: return None - if not isinstance(launch, KernelLaunch): - raise TypeError('kernel factory must return a KernelLaunch value') - - if factory.kernelTypes and not isinstance(launch.kernel, factory.kernelTypes): + if not isinstance(launch, CoreRuntimeLaunch): raise TypeError( - f'kernel factory {factory.factoryId!r} returned an unowned kernel' + 'core runtime factory must return a CoreRuntimeLaunch value' + ) + + runtimeTypes = _runtimeTypes(factory) + + if runtimeTypes and not isinstance(launch.runtime, runtimeTypes): + raise TypeError( + f'core runtime factory {factory.factoryId!r} returned an ' + f'unowned runtime' ) return launch - def startKernel(self, config, routing, **kwargs): - """Create and start the runtime kernel selected for *config*.""" + def startCoreRuntime(self, config, routing, **kwargs): + """Create and start the core runtime selected for *config*.""" try: - launch = self.createKernel(config, routing, **kwargs) + launch = self.createCoreRuntime(config, routing, **kwargs) return ( - (launch.kernel, launch.start()) if launch is not None else (None, False) + (launch.runtime, launch.start()) + if launch is not None + else (None, False) ) except Exception as ex: # Any non-exit exceptions factory = self.factoryForConfig(config) factoryId = factory.factoryId if factory is not None else 'unknown' - logger.error(f'kernel start failed for {factoryId!r}: {ex}') + logger.error(f'core runtime start failed for {factoryId!r}: {ex}') return None, False def prepareDownloadTest(self, config, port: int): - """Create a proxy-only test configuration through its kernel factory.""" + """Create a proxy-only test configuration through its runtime factory.""" factory = self.factoryForConfig(config) return ( @@ -1101,8 +1117,8 @@ class PluginRegistry: return None def configureEnvironment(self): - """Allow every kernel factory to configure its process environment.""" - for factory in self.kernelFactories(): + """Allow every runtime factory to configure its execution environment.""" + for factory in self.coreRuntimeFactories(): try: factory.configureEnvironment() except Exception as ex: @@ -1111,10 +1127,10 @@ class PluginRegistry: logger.error(f'environment hook failed for {factory.factoryId!r}: {ex}') def coreVersions(self): - """Return version strings reported by every kernel factory.""" + """Return version strings reported by every core-runtime factory.""" versions = [] - for factory in self.kernelFactories(): + for factory in self.coreRuntimeFactories(): try: versions.extend(factory.coreVersions()) except Exception as ex: @@ -1127,10 +1143,10 @@ class PluginRegistry: return tuple(filter(None, versions)) def logTimestampPatterns(self): - """Return timestamp expressions contributed by all kernel factories.""" + """Return timestamp expressions contributed by runtime factories.""" patterns = [] - for factory in self.kernelFactories(): + for factory in self.coreRuntimeFactories(): try: patterns.extend(factory.logTimestampPatterns()) except Exception as ex: @@ -1144,7 +1160,7 @@ class PluginRegistry: def coreExitMessage(self, core, exitcode: int): """Return the owning factory's special exit message, if any.""" - factory = self.factoryForKernel(core) + factory = self.runtimeFactoryFor(core) if factory is None: return None @@ -1161,8 +1177,8 @@ class PluginRegistry: return None def afterConnected(self, httpProxy=None): - """Notify every kernel factory after a connection succeeds.""" - for factory in self.kernelFactories(): + """Notify every core-runtime factory after a connection succeeds.""" + for factory in self.coreRuntimeFactories(): try: factory.afterConnected(httpProxy) except Exception as ex: diff --git a/Furious/Plugins/__init__.py b/Furious/Plugins/__init__.py index ab3a3bd0..d468c6d9 100644 --- a/Furious/Plugins/__init__.py +++ b/Furious/Plugins/__init__.py @@ -23,10 +23,10 @@ from .API import ( PLUGIN_API_VERSION, ActionProvider, CapabilityKind, + CoreRuntimeFactory, + CoreRuntimeLaunch, + CoreRuntimeRequest, FuriousPlugin, - KernelFactory, - KernelLaunch, - KernelRequest, NavigationPageDescriptor, NavigationPageProvider, PluginCapability, @@ -71,10 +71,10 @@ __all__ = [ 'PLUGIN_ENTRY_POINT_GROUP', 'ActionProvider', 'CapabilityKind', + 'CoreRuntimeFactory', + 'CoreRuntimeLaunch', + 'CoreRuntimeRequest', 'FuriousPlugin', - 'KernelFactory', - 'KernelLaunch', - 'KernelRequest', 'NavigationPageDescriptor', 'NavigationPageProvider', 'PluginCapability', diff --git a/Furious/Service/ConnectionManager.py b/Furious/Service/ConnectionManager.py index 4424e28c..b304e444 100644 --- a/Furious/Service/ConnectionManager.py +++ b/Furious/Service/ConnectionManager.py @@ -77,7 +77,7 @@ class ConnectionManager(Mixins.CleanupOnExit): super().__init__(*args, **kwargs) self.uniqueCleanup = False - self.processesPool = list() + self.runtimes = list() self._lastStartError = '' @property @@ -85,7 +85,7 @@ class ConnectionManager(Mixins.CleanupOnExit): """Return the concise failure reported by the latest runtime start.""" return self._lastStartError - def _startKernel( + def _startCoreRuntime( self, config, routing, @@ -94,9 +94,9 @@ class ConnectionManager(Mixins.CleanupOnExit): proxyModeOnly=False, log=True, **kwargs, - ) -> Tuple[Union[CoreProcess, None], bool]: - """Construct and start the runtime kernel selected for a configuration.""" - return getPluginRegistry().startKernel( + ) -> Tuple[Union[CoreRuntime, None], bool]: + """Construct and start the core runtime selected for a configuration.""" + return getPluginRegistry().startCoreRuntime( config, routing, exitCallback=exitCallback, @@ -178,10 +178,10 @@ class ConnectionManager(Mixins.CleanupOnExit): else: logger.info( 'application-managed tun2socks skipped by the active ' - 'kernel configuration' + 'core runtime configuration' ) - process, success = self._startKernel( + runtime, success = self._startCoreRuntime( configcopy, routing, exitCallback, @@ -191,18 +191,18 @@ class ConnectionManager(Mixins.CleanupOnExit): **kwargs, ) - if process is not None: - self.processesPool.append(process) + if runtime is not None: + self.runtimes.append(runtime) if not success: - if isinstance(process, CoreProcess): - startError = getattr(process, 'startError', None) + if isinstance(runtime, CoreRuntime): + startError = getattr(runtime, 'startError', None) if callable(startError): self._lastStartError = startError() - if isinstance(process, CoreProcessWorker): - logger.error(f'core {process.name()} start failed') + if isinstance(runtime, CoreProcessWorker): + logger.error(f'core {runtime.name()} start failed') self.stopAll() @@ -260,7 +260,7 @@ class ConnectionManager(Mixins.CleanupOnExit): return abortStart(f'unrecognized platform: {PLATFORM}') tun = Tun2socks(exitCallback=exitCallback, msgCallback=msgCallbackTUN_) - self.processesPool.append(tun) + self.runtimes.append(tun) tcpSendBufferSize, tcpReceiveBufferSize, tcpAutoTuning = ( userTcpSendBufferSize(), @@ -566,28 +566,28 @@ class ConnectionManager(Mixins.CleanupOnExit): return True def allRunning(self) -> bool: - """Return whether every managed process is running.""" - return all(process.isAlive() for process in self.processesPool) + """Return whether every managed core runtime is running.""" + return all(runtime.isAlive() for runtime in self.runtimes) def anyRunning(self) -> bool: - """Return whether any managed process is running.""" - return any(process.isAlive() for process in self.processesPool) + """Return whether any managed core runtime is running.""" + return any(runtime.isAlive() for runtime in self.runtimes) def stopAll(self): - """Stop every managed proxy and TUN process.""" + """Stop every managed proxy-core and TUN runtime.""" try: - for process in list(self.processesPool): - if not isinstance(process, CoreProcess): + for runtime in list(self.runtimes): + if not isinstance(runtime, CoreRuntime): continue try: - process.stop() + runtime.stop() except Exception as ex: # Any non-exit exceptions - logger.error(f'error stopping core process: {ex}') + logger.error(f'error stopping core runtime: {ex}') finally: - dispose = getattr(process, 'dispose', None) + dispose = getattr(runtime, 'dispose', None) if callable(dispose): try: @@ -595,9 +595,9 @@ class ConnectionManager(Mixins.CleanupOnExit): except Exception as ex: # Any non-exit exceptions - logger.error(f'error disposing core process: {ex}') + logger.error(f'error disposing core runtime: {ex}') finally: - self.processesPool.clear() + self.runtimes.clear() def cleanup(self): """Release resources owned by the core manager.""" diff --git a/Furious/Service/PluginUIManager.py b/Furious/Service/PluginUIManager.py index f0740116..95aa76ab 100644 --- a/Furious/Service/PluginUIManager.py +++ b/Furious/Service/PluginUIManager.py @@ -42,7 +42,7 @@ def isCoreActive(coreType) -> bool: controller = AppConnectionController() return controller.isConnected() and any( - isinstance(process, coreType) for process in controller.processes + isinstance(runtime, coreType) for runtime in controller.runtimes ) except (AttributeError, RuntimeError): return False diff --git a/Furious/Service/TrafficStatsManager.py b/Furious/Service/TrafficStatsManager.py index 3844da9b..a097f540 100644 --- a/Furious/Service/TrafficStatsManager.py +++ b/Furious/Service/TrafficStatsManager.py @@ -247,10 +247,10 @@ class TrafficStatsManager( self._requestSample() @staticmethod - def _activeProcesses(): - """Return the processes owned by the active connection controller.""" + def _activeRuntimes(): + """Return the runtimes owned by the active connection controller.""" try: - return AppConnectionController().processes + return AppConnectionController().runtimes except (AttributeError, RuntimeError): return tuple() @@ -369,8 +369,8 @@ class TrafficStatsManager( return if self._connected: - monitor = getPluginRegistry().trafficStatsMonitorForKernels( - self._activeProcesses() + monitor = getPluginRegistry().trafficStatsMonitorForRuntimes( + self._activeRuntimes() ) self._activateMonitor(monitor) @@ -504,8 +504,8 @@ class TrafficStatsManager( return - monitor = getPluginRegistry().trafficStatsMonitorForKernels( - self._activeProcesses() + monitor = getPluginRegistry().trafficStatsMonitorForRuntimes( + self._activeRuntimes() ) self._activateMonitor(monitor) diff --git a/Furious/Widget/ServerTableView.py b/Furious/Widget/ServerTableView.py index 37f61d2d..eaa5a936 100644 --- a/Furious/Widget/ServerTableView.py +++ b/Furious/Widget/ServerTableView.py @@ -561,13 +561,13 @@ class TestDownloadSpeedWorker(WebGETManager): def coreExitCallback(self, config: ConfigFactory, exitcode: int): """Handle the core exit callback.""" try: - if exitcode == CoreProcess.ExitCode.ConfigurationError.value: + if exitcode == CoreRuntime.ExitCode.ConfigurationError.value: self.factory.metadata.speed = 'Invalid' self.sync() - elif exitcode == CoreProcess.ExitCode.ServerStartFailure.value: + elif exitcode == CoreRuntime.ExitCode.ServerStartFailure.value: self.factory.metadata.speed = 'Core start failed' self.sync() - elif exitcode == CoreProcess.ExitCode.SystemShuttingDown.value: + elif exitcode == CoreRuntime.ExitCode.SystemShuttingDown.value: pass else: self.factory.metadata.speed = f'Core exited {exitcode}' @@ -575,7 +575,7 @@ class TestDownloadSpeedWorker(WebGETManager): finally: self.must() - def _startKernel(self, config) -> bool: + def _startCoreRuntime(self, config) -> bool: """Prepare and start a download test through its runtime factory.""" configcopy = getPluginRegistry().prepareDownloadTest(config, self.port) @@ -617,7 +617,7 @@ class TestDownloadSpeedWorker(WebGETManager): self.factory.metadata.speed = 'Invalid' self.sync() else: - if not self._startKernel(self.factory) or appIsExiting(): + if not self._startCoreRuntime(self.factory) or appIsExiting(): return self.configureHttpProxy(f'127.0.0.1:{self.port}')