@scrypted/server 0.123.21 → 0.123.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin/plugin-api.d.ts +1 -3
- package/dist/plugin/plugin-api.js +1 -3
- package/dist/plugin/plugin-api.js.map +1 -1
- package/dist/plugin/plugin-host.d.ts +0 -3
- package/dist/plugin/plugin-host.js +3 -16
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +1 -11
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/scrypted-cluster-main.d.ts +1 -0
- package/dist/scrypted-cluster-main.js +14 -1
- package/dist/scrypted-cluster-main.js.map +1 -1
- package/dist/services/cluster-fork.js +7 -1
- package/dist/services/cluster-fork.js.map +1 -1
- package/dist/services/plugin.d.ts +0 -1
- package/dist/services/plugin.js +0 -1
- package/dist/services/plugin.js.map +1 -1
- package/package.json +1 -1
- package/python/cluster_setup.py +227 -0
- package/python/plugin_remote.py +9 -272
- package/src/plugin/plugin-api.ts +2 -3
- package/src/plugin/plugin-host.ts +3 -16
- package/src/plugin/plugin-remote-worker.ts +1 -12
- package/src/scrypted-cluster-main.ts +17 -1
- package/src/services/cluster-fork.ts +7 -1
- package/src/services/plugin.ts +0 -1
- package/dist/plugin/plugin-remote-stats.d.ts +0 -7
- package/dist/plugin/plugin-remote-stats.js +0 -36
- package/dist/plugin/plugin-remote-stats.js.map +0 -1
- package/src/plugin/plugin-remote-stats.ts +0 -44
@@ -0,0 +1,227 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import base64
|
5
|
+
import hashlib
|
6
|
+
import os
|
7
|
+
from asyncio.events import AbstractEventLoop
|
8
|
+
from collections.abc import Mapping
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
import rpc
|
12
|
+
import rpc_reader
|
13
|
+
from typing import TypedDict
|
14
|
+
|
15
|
+
class ClusterObject(TypedDict):
|
16
|
+
id: str
|
17
|
+
address: str
|
18
|
+
port: int
|
19
|
+
proxyId: str
|
20
|
+
sourceKey: str
|
21
|
+
sha256: str
|
22
|
+
|
23
|
+
def isClusterAddress(address: str):
|
24
|
+
return not address or address == os.environ.get("SCRYPTED_CLUSTER_ADDRESS", None)
|
25
|
+
|
26
|
+
def getClusterPeerKey(address: str, port: int):
|
27
|
+
return f"{address}:{port}"
|
28
|
+
class ClusterSetup():
|
29
|
+
def __init__(self, loop: AbstractEventLoop, peer: rpc.RpcPeer):
|
30
|
+
self.loop = loop
|
31
|
+
self.peer = peer
|
32
|
+
self.clusterId: str = None
|
33
|
+
self.clusterSecret: str = None
|
34
|
+
self.clusterAddress: str = None
|
35
|
+
self.clusterPort: int = None
|
36
|
+
self.SCRYPTED_CLUSTER_ADDRESS: str = None
|
37
|
+
self.clusterPeers: Mapping[str, asyncio.Future[rpc.RpcPeer]] = {}
|
38
|
+
|
39
|
+
async def resolveObject(self, id: str, sourceKey: str):
|
40
|
+
sourcePeer: rpc.RpcPeer = (
|
41
|
+
self.peer
|
42
|
+
if not sourceKey
|
43
|
+
else await rpc.maybe_await(self.clusterPeers.get(sourceKey, None))
|
44
|
+
)
|
45
|
+
if not sourcePeer:
|
46
|
+
return
|
47
|
+
return sourcePeer.localProxyMap.get(id, None)
|
48
|
+
|
49
|
+
async def connectClusterObject(self, o: ClusterObject):
|
50
|
+
sha256 = self.computeClusterObjectHash(o)
|
51
|
+
if sha256 != o["sha256"]:
|
52
|
+
raise Exception("secret incorrect")
|
53
|
+
return await self.resolveObject(o.get('proxyId', None), o.get('sourceKey', None))
|
54
|
+
|
55
|
+
def onProxySerialization(self, peer: rpc.RpcPeer, value: Any, sourceKey: str = None):
|
56
|
+
properties: dict = rpc.RpcPeer.prepareProxyProperties(value) or {}
|
57
|
+
clusterEntry = properties.get("__cluster", None)
|
58
|
+
proxyId: str
|
59
|
+
existing = peer.localProxied.get(value, None)
|
60
|
+
if existing:
|
61
|
+
proxyId = existing["id"]
|
62
|
+
else:
|
63
|
+
proxyId = (
|
64
|
+
clusterEntry and clusterEntry.get("proxyId", None)
|
65
|
+
) or rpc.RpcPeer.generateId()
|
66
|
+
|
67
|
+
if clusterEntry:
|
68
|
+
if (
|
69
|
+
isClusterAddress(clusterEntry.get("address", None))
|
70
|
+
and self.clusterPort == clusterEntry["port"]
|
71
|
+
and sourceKey != clusterEntry.get("sourceKey", None)
|
72
|
+
):
|
73
|
+
clusterEntry = None
|
74
|
+
|
75
|
+
if not clusterEntry:
|
76
|
+
clusterEntry: ClusterObject = {
|
77
|
+
"id": self.clusterId,
|
78
|
+
"proxyId": proxyId,
|
79
|
+
"address": self.SCRYPTED_CLUSTER_ADDRESS,
|
80
|
+
"port": self.clusterPort,
|
81
|
+
"sourceKey": sourceKey,
|
82
|
+
}
|
83
|
+
clusterEntry["sha256"] = self.computeClusterObjectHash(clusterEntry)
|
84
|
+
properties["__cluster"] = clusterEntry
|
85
|
+
|
86
|
+
return proxyId, properties
|
87
|
+
|
88
|
+
async def initializeCluster(self, options: dict):
|
89
|
+
if self.clusterPort:
|
90
|
+
return
|
91
|
+
self.clusterId = options["clusterId"]
|
92
|
+
self.clusterSecret = options["clusterSecret"]
|
93
|
+
self.SCRYPTED_CLUSTER_ADDRESS = os.environ.get("SCRYPTED_CLUSTER_ADDRESS", None)
|
94
|
+
|
95
|
+
async def handleClusterClient(
|
96
|
+
reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
97
|
+
):
|
98
|
+
clusterPeerAddress, clusterPeerPort = writer.get_extra_info("peername")
|
99
|
+
clusterPeerKey = getClusterPeerKey(clusterPeerAddress, clusterPeerPort)
|
100
|
+
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
101
|
+
peer: rpc.RpcPeer
|
102
|
+
peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
103
|
+
self.loop, rpcTransport
|
104
|
+
)
|
105
|
+
# set all params from self.peer
|
106
|
+
for key, value in self.peer.params.items():
|
107
|
+
peer.params[key] = value
|
108
|
+
peer.onProxySerialization = lambda value: self.onProxySerialization(
|
109
|
+
peer, value, clusterPeerKey
|
110
|
+
)
|
111
|
+
future: asyncio.Future[rpc.RpcPeer] = asyncio.Future()
|
112
|
+
future.set_result(peer)
|
113
|
+
self.clusterPeers[clusterPeerKey] = future
|
114
|
+
peer.params["connectRPCObject"] = lambda o: self.connectClusterObject(o)
|
115
|
+
try:
|
116
|
+
await peerReadLoop()
|
117
|
+
except:
|
118
|
+
pass
|
119
|
+
finally:
|
120
|
+
self.clusterPeers.pop(clusterPeerKey)
|
121
|
+
peer.kill("cluster client killed")
|
122
|
+
writer.close()
|
123
|
+
|
124
|
+
listenAddress = "0.0.0.0" if self.SCRYPTED_CLUSTER_ADDRESS else "127.0.0.1"
|
125
|
+
clusterRpcServer = await asyncio.start_server(
|
126
|
+
handleClusterClient, listenAddress, 0
|
127
|
+
)
|
128
|
+
self.clusterPort = clusterRpcServer.sockets[0].getsockname()[1]
|
129
|
+
self.peer.onProxySerialization = lambda value: self.onProxySerialization(self.peer, value, None)
|
130
|
+
del self.peer.params["initializeCluster"]
|
131
|
+
|
132
|
+
def computeClusterObjectHash(self, o: ClusterObject) -> str:
|
133
|
+
m = hashlib.sha256()
|
134
|
+
m.update(
|
135
|
+
bytes(
|
136
|
+
f"{o['id']}{o.get('address') or ''}{o['port']}{o.get('sourceKey', None) or ''}{o['proxyId']}{self.clusterSecret}",
|
137
|
+
"utf8",
|
138
|
+
)
|
139
|
+
)
|
140
|
+
return base64.b64encode(m.digest()).decode("utf-8")
|
141
|
+
|
142
|
+
def ensureClusterPeer(self, address: str, port: int):
|
143
|
+
if isClusterAddress(address):
|
144
|
+
address = "127.0.0.1"
|
145
|
+
clusterPeerKey = getClusterPeerKey(address, port)
|
146
|
+
clusterPeerPromise = self.clusterPeers.get(clusterPeerKey)
|
147
|
+
if clusterPeerPromise:
|
148
|
+
return clusterPeerPromise
|
149
|
+
|
150
|
+
async def connectClusterPeer():
|
151
|
+
try:
|
152
|
+
reader, writer = await asyncio.open_connection(address, port)
|
153
|
+
sourceAddress, sourcePort = writer.get_extra_info("sockname")
|
154
|
+
if (
|
155
|
+
sourceAddress != self.SCRYPTED_CLUSTER_ADDRESS
|
156
|
+
and sourceAddress != "127.0.0.1"
|
157
|
+
):
|
158
|
+
print("source address mismatch", sourceAddress)
|
159
|
+
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
160
|
+
clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
161
|
+
self.loop, rpcTransport
|
162
|
+
)
|
163
|
+
# set all params from self.peer
|
164
|
+
for key, value in self.peer.params.items():
|
165
|
+
clusterPeer.params[key] = value
|
166
|
+
clusterPeer.onProxySerialization = (
|
167
|
+
lambda value: self.clusterSetup.onProxySerialization(
|
168
|
+
clusterPeer, value, clusterPeerKey
|
169
|
+
)
|
170
|
+
)
|
171
|
+
except:
|
172
|
+
self.clusterPeers.pop(clusterPeerKey)
|
173
|
+
raise
|
174
|
+
|
175
|
+
async def run_loop():
|
176
|
+
try:
|
177
|
+
await peerReadLoop()
|
178
|
+
except:
|
179
|
+
pass
|
180
|
+
finally:
|
181
|
+
self.clusterPeers.pop(clusterPeerKey)
|
182
|
+
|
183
|
+
asyncio.run_coroutine_threadsafe(run_loop(), self.loop)
|
184
|
+
return clusterPeer
|
185
|
+
|
186
|
+
clusterPeerPromise = self.loop.create_task(connectClusterPeer())
|
187
|
+
|
188
|
+
self.clusterPeers[clusterPeerKey] = clusterPeerPromise
|
189
|
+
return clusterPeerPromise
|
190
|
+
|
191
|
+
async def connectRPCObject(self, value):
|
192
|
+
__cluster = getattr(value, "__cluster")
|
193
|
+
if type(__cluster) is not dict:
|
194
|
+
return value
|
195
|
+
|
196
|
+
clusterObject: ClusterObject = __cluster
|
197
|
+
|
198
|
+
if clusterObject.get("id", None) != self.clusterId:
|
199
|
+
return value
|
200
|
+
|
201
|
+
address = clusterObject.get("address", None)
|
202
|
+
port = clusterObject["port"]
|
203
|
+
proxyId = clusterObject["proxyId"]
|
204
|
+
if port == self.clusterPort:
|
205
|
+
return await self.connectRPCObject(clusterObject)
|
206
|
+
|
207
|
+
clusterPeerPromise = self.ensureClusterPeer(address, port)
|
208
|
+
|
209
|
+
try:
|
210
|
+
clusterPeer = await clusterPeerPromise
|
211
|
+
weakref = clusterPeer.remoteWeakProxies.get(proxyId, None)
|
212
|
+
existing = weakref() if weakref else None
|
213
|
+
if existing:
|
214
|
+
return existing
|
215
|
+
|
216
|
+
peerConnectRPCObject = clusterPeer.tags.get("connectRPCObject")
|
217
|
+
if not peerConnectRPCObject:
|
218
|
+
peerConnectRPCObject = await clusterPeer.getParam(
|
219
|
+
"connectRPCObject"
|
220
|
+
)
|
221
|
+
clusterPeer.tags["connectRPCObject"] = peerConnectRPCObject
|
222
|
+
newValue = await peerConnectRPCObject(clusterObject)
|
223
|
+
if not newValue:
|
224
|
+
raise Exception("rpc object not found?")
|
225
|
+
return newValue
|
226
|
+
except Exception as e:
|
227
|
+
return value
|
package/python/plugin_remote.py
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import base64
|
5
4
|
import gc
|
6
|
-
import hashlib
|
7
5
|
import inspect
|
8
6
|
import multiprocessing
|
9
7
|
import multiprocessing.connection
|
@@ -19,39 +17,27 @@ from asyncio.futures import Future
|
|
19
17
|
from asyncio.streams import StreamReader, StreamWriter
|
20
18
|
from collections.abc import Mapping
|
21
19
|
from io import StringIO
|
22
|
-
from typing import Any, Optional, Set, Tuple, TypedDict
|
20
|
+
from typing import Any, Callable, Coroutine, Optional, Set, Tuple, TypedDict
|
23
21
|
|
24
22
|
import plugin_volume as pv
|
25
23
|
import rpc
|
26
24
|
import rpc_reader
|
27
25
|
import scrypted_python.scrypted_sdk.types
|
26
|
+
from cluster_setup import ClusterSetup
|
28
27
|
from plugin_pip import install_with_pip, need_requirements, remove_pip_dirs
|
29
28
|
from scrypted_python.scrypted_sdk import PluginFork, ScryptedStatic
|
30
|
-
from scrypted_python.scrypted_sdk.types import (
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
ScryptedInterfaceProperty,
|
37
|
-
Storage,
|
38
|
-
)
|
29
|
+
from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest,
|
30
|
+
EventDetails,
|
31
|
+
ScryptedInterface,
|
32
|
+
ScryptedInterfaceMethods,
|
33
|
+
ScryptedInterfaceProperty,
|
34
|
+
Storage)
|
39
35
|
|
40
36
|
SCRYPTED_REQUIREMENTS = """
|
41
37
|
ptpython
|
42
38
|
wheel
|
43
39
|
""".strip()
|
44
40
|
|
45
|
-
|
46
|
-
class ClusterObject(TypedDict):
|
47
|
-
id: str
|
48
|
-
address: str
|
49
|
-
port: int
|
50
|
-
proxyId: str
|
51
|
-
sourceKey: str
|
52
|
-
sha256: str
|
53
|
-
|
54
|
-
|
55
41
|
class SystemDeviceState(TypedDict):
|
56
42
|
lastEventTime: int
|
57
43
|
stateTime: int
|
@@ -553,214 +539,6 @@ class DeviceManager(scrypted_python.scrypted_sdk.types.DeviceManager):
|
|
553
539
|
def getDeviceStorage(self, nativeId: str = None) -> Storage:
|
554
540
|
return self.nativeIds.get(nativeId, None)
|
555
541
|
|
556
|
-
|
557
|
-
def isClusterAddress(address: str):
|
558
|
-
return not address or address == os.environ.get("SCRYPTED_CLUSTER_ADDRESS", None)
|
559
|
-
|
560
|
-
def getClusterPeerKey(address: str, port: int):
|
561
|
-
return f"{address}:{port}"
|
562
|
-
|
563
|
-
class ClusterSetup():
|
564
|
-
def __init__(self, loop: AbstractEventLoop, peer: rpc.RpcPeer):
|
565
|
-
self.loop = loop
|
566
|
-
self.peer = peer
|
567
|
-
self.clusterId: str = None
|
568
|
-
self.clusterSecret: str = None
|
569
|
-
self.clusterAddress: str = None
|
570
|
-
self.clusterPort: int = None
|
571
|
-
self.SCRYPTED_CLUSTER_ADDRESS: str = None
|
572
|
-
self.clusterPeers: Mapping[str, asyncio.Future[rpc.RpcPeer]] = {}
|
573
|
-
|
574
|
-
async def resolveObject(self, id: str, sourceKey: str):
|
575
|
-
sourcePeer: rpc.RpcPeer = (
|
576
|
-
self.peer
|
577
|
-
if not sourceKey
|
578
|
-
else await rpc.maybe_await(self.clusterPeers.get(sourceKey, None))
|
579
|
-
)
|
580
|
-
if not sourcePeer:
|
581
|
-
return
|
582
|
-
return sourcePeer.localProxyMap.get(id, None)
|
583
|
-
|
584
|
-
async def connectClusterObject(self, o: ClusterObject):
|
585
|
-
sha256 = self.computeClusterObjectHash(o)
|
586
|
-
if sha256 != o["sha256"]:
|
587
|
-
raise Exception("secret incorrect")
|
588
|
-
return await self.resolveObject(o.get('proxyId', None), o.get('sourceKey', None))
|
589
|
-
|
590
|
-
def onProxySerialization(self, peer: rpc.RpcPeer, value: Any, sourceKey: str = None):
|
591
|
-
properties: dict = rpc.RpcPeer.prepareProxyProperties(value) or {}
|
592
|
-
clusterEntry = properties.get("__cluster", None)
|
593
|
-
proxyId: str
|
594
|
-
existing = peer.localProxied.get(value, None)
|
595
|
-
if existing:
|
596
|
-
proxyId = existing["id"]
|
597
|
-
else:
|
598
|
-
proxyId = (
|
599
|
-
clusterEntry and clusterEntry.get("proxyId", None)
|
600
|
-
) or rpc.RpcPeer.generateId()
|
601
|
-
|
602
|
-
if clusterEntry:
|
603
|
-
if (
|
604
|
-
isClusterAddress(clusterEntry.get("address", None))
|
605
|
-
and self.clusterPort == clusterEntry["port"]
|
606
|
-
and sourceKey != clusterEntry.get("sourceKey", None)
|
607
|
-
):
|
608
|
-
clusterEntry = None
|
609
|
-
|
610
|
-
if not clusterEntry:
|
611
|
-
clusterEntry: ClusterObject = {
|
612
|
-
"id": self.clusterId,
|
613
|
-
"proxyId": proxyId,
|
614
|
-
"address": self.SCRYPTED_CLUSTER_ADDRESS,
|
615
|
-
"port": self.clusterPort,
|
616
|
-
"sourceKey": sourceKey,
|
617
|
-
}
|
618
|
-
clusterEntry["sha256"] = self.computeClusterObjectHash(clusterEntry)
|
619
|
-
properties["__cluster"] = clusterEntry
|
620
|
-
|
621
|
-
return proxyId, properties
|
622
|
-
|
623
|
-
async def initializeCluster(self, options: dict):
|
624
|
-
if self.clusterPort:
|
625
|
-
return
|
626
|
-
self.clusterId = options["clusterId"]
|
627
|
-
self.clusterSecret = options["clusterSecret"]
|
628
|
-
self.SCRYPTED_CLUSTER_ADDRESS = os.environ.get("SCRYPTED_CLUSTER_ADDRESS", None)
|
629
|
-
|
630
|
-
async def handleClusterClient(
|
631
|
-
reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
632
|
-
):
|
633
|
-
clusterPeerAddress, clusterPeerPort = writer.get_extra_info("peername")
|
634
|
-
clusterPeerKey = getClusterPeerKey(clusterPeerAddress, clusterPeerPort)
|
635
|
-
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
636
|
-
peer: rpc.RpcPeer
|
637
|
-
peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
638
|
-
self.loop, rpcTransport
|
639
|
-
)
|
640
|
-
# set all params from self.peer
|
641
|
-
for key, value in self.peer.params.items():
|
642
|
-
peer.params[key] = value
|
643
|
-
peer.onProxySerialization = lambda value: self.onProxySerialization(
|
644
|
-
peer, value, clusterPeerKey
|
645
|
-
)
|
646
|
-
future: asyncio.Future[rpc.RpcPeer] = asyncio.Future()
|
647
|
-
future.set_result(peer)
|
648
|
-
self.clusterPeers[clusterPeerKey] = future
|
649
|
-
peer.params["connectRPCObject"] = lambda o: self.connectClusterObject(o)
|
650
|
-
try:
|
651
|
-
await peerReadLoop()
|
652
|
-
except:
|
653
|
-
pass
|
654
|
-
finally:
|
655
|
-
self.clusterPeers.pop(clusterPeerKey)
|
656
|
-
peer.kill("cluster client killed")
|
657
|
-
writer.close()
|
658
|
-
|
659
|
-
listenAddress = "0.0.0.0" if self.SCRYPTED_CLUSTER_ADDRESS else "127.0.0.1"
|
660
|
-
clusterRpcServer = await asyncio.start_server(
|
661
|
-
handleClusterClient, listenAddress, 0
|
662
|
-
)
|
663
|
-
self.clusterPort = clusterRpcServer.sockets[0].getsockname()[1]
|
664
|
-
self.peer.onProxySerialization = lambda value: self.onProxySerialization(self.peer, value, None)
|
665
|
-
del self.peer.params["initializeCluster"]
|
666
|
-
|
667
|
-
def computeClusterObjectHash(self, o: ClusterObject) -> str:
|
668
|
-
m = hashlib.sha256()
|
669
|
-
m.update(
|
670
|
-
bytes(
|
671
|
-
f"{o['id']}{o.get('address') or ''}{o['port']}{o.get('sourceKey', None) or ''}{o['proxyId']}{self.clusterSecret}",
|
672
|
-
"utf8",
|
673
|
-
)
|
674
|
-
)
|
675
|
-
return base64.b64encode(m.digest()).decode("utf-8")
|
676
|
-
|
677
|
-
def ensureClusterPeer(self, address: str, port: int):
|
678
|
-
if isClusterAddress(address):
|
679
|
-
address = "127.0.0.1"
|
680
|
-
clusterPeerKey = getClusterPeerKey(address, port)
|
681
|
-
clusterPeerPromise = self.clusterPeers.get(clusterPeerKey)
|
682
|
-
if clusterPeerPromise:
|
683
|
-
return clusterPeerPromise
|
684
|
-
|
685
|
-
async def connectClusterPeer():
|
686
|
-
try:
|
687
|
-
reader, writer = await asyncio.open_connection(address, port)
|
688
|
-
sourceAddress, sourcePort = writer.get_extra_info("sockname")
|
689
|
-
if (
|
690
|
-
sourceAddress != self.SCRYPTED_CLUSTER_ADDRESS
|
691
|
-
and sourceAddress != "127.0.0.1"
|
692
|
-
):
|
693
|
-
print("source address mismatch", sourceAddress)
|
694
|
-
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
695
|
-
clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
696
|
-
self.loop, rpcTransport
|
697
|
-
)
|
698
|
-
# set all params from self.peer
|
699
|
-
for key, value in self.peer.params.items():
|
700
|
-
clusterPeer.params[key] = value
|
701
|
-
clusterPeer.onProxySerialization = (
|
702
|
-
lambda value: self.clusterSetup.onProxySerialization(
|
703
|
-
clusterPeer, value, clusterPeerKey
|
704
|
-
)
|
705
|
-
)
|
706
|
-
except:
|
707
|
-
self.clusterPeers.pop(clusterPeerKey)
|
708
|
-
raise
|
709
|
-
|
710
|
-
async def run_loop():
|
711
|
-
try:
|
712
|
-
await peerReadLoop()
|
713
|
-
except:
|
714
|
-
pass
|
715
|
-
finally:
|
716
|
-
self.clusterPeers.pop(clusterPeerKey)
|
717
|
-
|
718
|
-
asyncio.run_coroutine_threadsafe(run_loop(), self.loop)
|
719
|
-
return clusterPeer
|
720
|
-
|
721
|
-
clusterPeerPromise = self.loop.create_task(connectClusterPeer())
|
722
|
-
|
723
|
-
self.clusterPeers[clusterPeerKey] = clusterPeerPromise
|
724
|
-
return clusterPeerPromise
|
725
|
-
|
726
|
-
async def connectRPCObject(self, value):
|
727
|
-
__cluster = getattr(value, "__cluster")
|
728
|
-
if type(__cluster) is not dict:
|
729
|
-
return value
|
730
|
-
|
731
|
-
clusterObject: ClusterObject = __cluster
|
732
|
-
|
733
|
-
if clusterObject.get("id", None) != self.clusterId:
|
734
|
-
return value
|
735
|
-
|
736
|
-
address = clusterObject.get("address", None)
|
737
|
-
port = clusterObject["port"]
|
738
|
-
proxyId = clusterObject["proxyId"]
|
739
|
-
if port == self.clusterPort:
|
740
|
-
return await self.connectRPCObject(clusterObject)
|
741
|
-
|
742
|
-
clusterPeerPromise = self.ensureClusterPeer(address, port)
|
743
|
-
|
744
|
-
try:
|
745
|
-
clusterPeer = await clusterPeerPromise
|
746
|
-
weakref = clusterPeer.remoteWeakProxies.get(proxyId, None)
|
747
|
-
existing = weakref() if weakref else None
|
748
|
-
if existing:
|
749
|
-
return existing
|
750
|
-
|
751
|
-
peerConnectRPCObject = clusterPeer.tags.get("connectRPCObject")
|
752
|
-
if not peerConnectRPCObject:
|
753
|
-
peerConnectRPCObject = await clusterPeer.getParam(
|
754
|
-
"connectRPCObject"
|
755
|
-
)
|
756
|
-
clusterPeer.tags["connectRPCObject"] = peerConnectRPCObject
|
757
|
-
newValue = await peerConnectRPCObject(clusterObject)
|
758
|
-
if not newValue:
|
759
|
-
raise Exception("rpc object not found?")
|
760
|
-
return newValue
|
761
|
-
except Exception as e:
|
762
|
-
return value
|
763
|
-
|
764
542
|
class PluginRemote:
|
765
543
|
def __init__(
|
766
544
|
self, clusterSetup: ClusterSetup, api, pluginId: str, hostInfo, loop: AbstractEventLoop
|
@@ -769,8 +547,6 @@ class PluginRemote:
|
|
769
547
|
self.nativeIds: Mapping[str, DeviceStorage] = {}
|
770
548
|
self.mediaManager: MediaManager
|
771
549
|
self.consoles: Mapping[str, Future[Tuple[StreamReader, StreamWriter]]] = {}
|
772
|
-
self.ptimeSum = 0
|
773
|
-
self.allMemoryStats = {}
|
774
550
|
self.peer = clusterSetup.peer
|
775
551
|
self.clusterSetup = clusterSetup
|
776
552
|
self.api = api
|
@@ -977,8 +753,6 @@ class PluginRemote:
|
|
977
753
|
self.deviceManager = DeviceManager(self.nativeIds, self.systemManager)
|
978
754
|
self.mediaManager = MediaManager(await self.api.getMediaManager())
|
979
755
|
|
980
|
-
await self.start_stats_runner(zipAPI.updateStats)
|
981
|
-
|
982
756
|
try:
|
983
757
|
from scrypted_sdk import sdk_init2 # type: ignore
|
984
758
|
|
@@ -1023,7 +797,6 @@ class PluginRemote:
|
|
1023
797
|
# traceback.print_exc()
|
1024
798
|
print("fork read loop exited")
|
1025
799
|
finally:
|
1026
|
-
self.allMemoryStats.pop(forkPeer)
|
1027
800
|
parent_conn.close()
|
1028
801
|
rpcTransport.executor.shutdown()
|
1029
802
|
pluginFork.worker.kill()
|
@@ -1041,10 +814,7 @@ class PluginRemote:
|
|
1041
814
|
forkOptions["debug"] = debug
|
1042
815
|
|
1043
816
|
class PluginZipAPI:
|
1044
|
-
|
1045
|
-
self.ptimeSum += stats["cpu"]["user"]
|
1046
|
-
self.allMemoryStats[forkPeer] = stats
|
1047
|
-
|
817
|
+
|
1048
818
|
async def getZip(self):
|
1049
819
|
return await zipAPI.getZip()
|
1050
820
|
|
@@ -1140,39 +910,6 @@ class PluginRemote:
|
|
1140
910
|
return [self.replPort, os.getenv("SCRYPTED_CLUSTER_ADDRESS", None)]
|
1141
911
|
raise Exception(f"unknown service {name}")
|
1142
912
|
|
1143
|
-
async def start_stats_runner(self, update_stats):
|
1144
|
-
def stats_runner():
|
1145
|
-
ptime = round(time.process_time() * 1000000) + self.ptimeSum
|
1146
|
-
try:
|
1147
|
-
import psutil
|
1148
|
-
|
1149
|
-
process = psutil.Process(os.getpid())
|
1150
|
-
heapTotal = process.memory_info().rss
|
1151
|
-
except:
|
1152
|
-
try:
|
1153
|
-
import resource
|
1154
|
-
|
1155
|
-
heapTotal = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
1156
|
-
except:
|
1157
|
-
heapTotal = 0
|
1158
|
-
|
1159
|
-
for _, stats in self.allMemoryStats.items():
|
1160
|
-
heapTotal += stats["memoryUsage"]["heapTotal"]
|
1161
|
-
|
1162
|
-
stats = {
|
1163
|
-
"cpu": {
|
1164
|
-
"user": ptime,
|
1165
|
-
"system": 0,
|
1166
|
-
},
|
1167
|
-
"memoryUsage": {
|
1168
|
-
"heapTotal": heapTotal,
|
1169
|
-
},
|
1170
|
-
}
|
1171
|
-
asyncio.run_coroutine_threadsafe(update_stats(stats), self.loop)
|
1172
|
-
self.loop.call_later(10, stats_runner)
|
1173
|
-
|
1174
|
-
stats_runner()
|
1175
|
-
|
1176
913
|
|
1177
914
|
async def plugin_async_main(
|
1178
915
|
loop: AbstractEventLoop, rpcTransport: rpc_reader.RpcTransport
|
package/src/plugin/plugin-api.ts
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import type { Device, DeviceManifest, EventDetails, EventListenerOptions, EventListenerRegister, MediaManager, MediaObject, ScryptedDevice, ScryptedInterfaceDescriptor, ScryptedInterfaceProperty, ScryptedNativeId, SystemDeviceState } from '@scrypted/types';
|
2
2
|
import type { AccessControls } from './acl';
|
3
|
-
import type { PluginStats } from './plugin-remote-stats';
|
4
3
|
|
5
4
|
export interface PluginLogger {
|
6
5
|
log(level: string, message: string): Promise<void>;
|
@@ -170,8 +169,8 @@ export interface PluginRemoteLoadZipOptions {
|
|
170
169
|
|
171
170
|
export class PluginZipAPI {
|
172
171
|
constructor(
|
173
|
-
public getZip: () => Promise<Buffer
|
174
|
-
|
172
|
+
public getZip: () => Promise<Buffer>
|
173
|
+
) {
|
175
174
|
}
|
176
175
|
}
|
177
176
|
|
@@ -4,13 +4,13 @@ import * as io from 'engine.io';
|
|
4
4
|
import fs from 'fs';
|
5
5
|
import os from 'os';
|
6
6
|
import WebSocket from 'ws';
|
7
|
+
import { setupCluster } from '../cluster/cluster-setup';
|
7
8
|
import { Plugin } from '../db-types';
|
8
9
|
import { IOServer, IOServerSocket } from '../io';
|
9
10
|
import { Logger } from '../logger';
|
10
11
|
import { RpcPeer, RPCResultError } from '../rpc';
|
11
12
|
import { createRpcSerializer } from '../rpc-serializer';
|
12
13
|
import { ScryptedRuntime } from '../runtime';
|
13
|
-
import { setupCluster } from '../cluster/cluster-setup';
|
14
14
|
import { sleep } from '../sleep';
|
15
15
|
import { AccessControls } from './acl';
|
16
16
|
import { MediaManagerHostImpl } from './media';
|
@@ -20,7 +20,6 @@ import { PluginDebug } from './plugin-debug';
|
|
20
20
|
import { PluginHostAPI } from './plugin-host-api';
|
21
21
|
import { LazyRemote } from './plugin-lazy-remote';
|
22
22
|
import { setupPluginRemote } from './plugin-remote';
|
23
|
-
import { PluginStats } from './plugin-remote-stats';
|
24
23
|
import { WebSocketConnection } from './plugin-remote-websocket';
|
25
24
|
import { ensurePluginVolume, getScryptedVolume } from './plugin-volume';
|
26
25
|
import { createClusterForkWorker, needsClusterForkWorker } from './runtime/cluster-fork.worker';
|
@@ -60,8 +59,6 @@ export class PluginHost {
|
|
60
59
|
api: PluginHostAPI;
|
61
60
|
pluginName: string;
|
62
61
|
packageJson: any;
|
63
|
-
lastStats: number;
|
64
|
-
stats: PluginStats;
|
65
62
|
killed = false;
|
66
63
|
consoleServer: Promise<ConsoleServer>;
|
67
64
|
zipHash: string;
|
@@ -259,12 +256,7 @@ export class PluginHost {
|
|
259
256
|
// original implementation sent the zipBuffer, sending the zipFile name now.
|
260
257
|
// can switch back for non-local plugins.
|
261
258
|
const modulePromise = remote.loadZip(this.packageJson,
|
262
|
-
|
263
|
-
// this can be used as a check for liveness.
|
264
|
-
new PluginZipAPI(async () => fs.promises.readFile(this.zipFile), async (stats: PluginStats) => {
|
265
|
-
this.lastStats = Date.now();
|
266
|
-
this.stats = stats;
|
267
|
-
}),
|
259
|
+
new PluginZipAPI(async () => fs.promises.readFile(this.zipFile)),
|
268
260
|
loadZipOptions);
|
269
261
|
// allow garbage collection of the zip buffer
|
270
262
|
const module = await modulePromise;
|
@@ -414,7 +406,7 @@ export class PluginHost {
|
|
414
406
|
const now = Date.now();
|
415
407
|
// plugin may take a while to install, so wait 10 minutes.
|
416
408
|
// after that, require 1 minute checkins.
|
417
|
-
if (!
|
409
|
+
if (!lastPong) {
|
418
410
|
if (now - startupTime > 10 * 60 * 1000) {
|
419
411
|
const logger = await this.api.getLogger(undefined);
|
420
412
|
logger.log('e', 'plugin failed to start in a timely manner. restarting.');
|
@@ -422,11 +414,6 @@ export class PluginHost {
|
|
422
414
|
}
|
423
415
|
return;
|
424
416
|
}
|
425
|
-
if (!pluginDebug && (this.lastStats + 60000 < now)) {
|
426
|
-
const logger = await this.api.getLogger(undefined);
|
427
|
-
logger.log('e', 'plugin is not reporting stats. restarting.');
|
428
|
-
this.api.requestRestart();
|
429
|
-
}
|
430
417
|
if (!pluginDebug && (lastPong + 60000 < now)) {
|
431
418
|
const logger = await this.api.getLogger(undefined);
|
432
419
|
logger.log('e', 'plugin is not responding to ping. restarting.');
|