mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-31 07:18:25 +02:00
Explicit meshroom node type in status file
Avoid an ambiguous LOCAL_ISOLATED, as a process can be extern and using an isolated execution environement.
This commit is contained in:
parent
426855baa6
commit
eb9df4c900
6 changed files with 76 additions and 33 deletions
|
@ -83,9 +83,9 @@ if args.node:
|
||||||
chunks = node.chunks
|
chunks = node.chunks
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
if chunk.status.status in submittedStatuses:
|
if chunk.status.status in submittedStatuses:
|
||||||
# Particular case for the LOCAL_ISOLATED, the node status is set to RUNNING by the submitter directly.
|
# Particular case for the local isolated, the node status is set to RUNNING by the submitter directly.
|
||||||
# We ensure that no other instance has started to compute, by checking that the sessionUid is empty.
|
# We ensure that no other instance has started to compute, by checking that the sessionUid is empty.
|
||||||
if chunk.status.execMode == ExecMode.LOCAL_ISOLATED and not chunk.status.sessionUid and chunk.status.submitterSessionUid:
|
if chunk.status.mrNodeType == meshroom.core.MrNodeType.NODE and not chunk.status.sessionUid and chunk.status.submitterSessionUid:
|
||||||
continue
|
continue
|
||||||
print(f'Warning: Node is already submitted with status "{chunk.status.status.name}". See file: "{chunk.statusFile}". ExecMode: {chunk.status.execMode.name}, SessionUid: {chunk.status.sessionUid}, submitterSessionUid: {chunk.status.submitterSessionUid}')
|
print(f'Warning: Node is already submitted with status "{chunk.status.status.name}". See file: "{chunk.statusFile}". ExecMode: {chunk.status.execMode.name}, SessionUid: {chunk.status.sessionUid}, submitterSessionUid: {chunk.status.submitterSessionUid}')
|
||||||
# sys.exit(-1)
|
# sys.exit(-1)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import hashlib
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
import hashlib
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
import uuid
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
import uuid
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# for cx_freeze
|
# for cx_freeze
|
||||||
|
@ -21,7 +21,7 @@ except Exception:
|
||||||
from meshroom.core.submitter import BaseSubmitter
|
from meshroom.core.submitter import BaseSubmitter
|
||||||
from meshroom.env import EnvVar, meshroomFolder
|
from meshroom.env import EnvVar, meshroomFolder
|
||||||
from . import desc
|
from . import desc
|
||||||
|
from .desc import MrNodeType
|
||||||
|
|
||||||
# Setup logging
|
# Setup logging
|
||||||
logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=logging.INFO)
|
logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
|
@ -36,6 +36,7 @@ submitters = {}
|
||||||
pipelineTemplates = {}
|
pipelineTemplates = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def hashValue(value):
|
def hashValue(value):
|
||||||
""" Hash 'value' using sha1. """
|
""" Hash 'value' using sha1. """
|
||||||
hashObject = hashlib.sha1(str(value).encode('utf-8'))
|
hashObject = hashlib.sha1(str(value).encode('utf-8'))
|
||||||
|
|
|
@ -20,6 +20,7 @@ from .computation import (
|
||||||
StaticNodeSize,
|
StaticNodeSize,
|
||||||
)
|
)
|
||||||
from .node import (
|
from .node import (
|
||||||
|
MrNodeType,
|
||||||
AVCommandLineNode,
|
AVCommandLineNode,
|
||||||
BaseNode,
|
BaseNode,
|
||||||
CommandLineNode,
|
CommandLineNode,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import enum
|
||||||
from inspect import getfile
|
from inspect import getfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
|
@ -13,11 +14,17 @@ from .attribute import StringParam, ColorParam
|
||||||
import meshroom
|
import meshroom
|
||||||
from meshroom.core import cgroup
|
from meshroom.core import cgroup
|
||||||
|
|
||||||
|
|
||||||
_MESHROOM_ROOT = Path(meshroom.__file__).parent.parent
|
_MESHROOM_ROOT = Path(meshroom.__file__).parent.parent
|
||||||
_MESHROOM_COMPUTE = _MESHROOM_ROOT / "bin" / "meshroom_compute"
|
_MESHROOM_COMPUTE = _MESHROOM_ROOT / "bin" / "meshroom_compute"
|
||||||
|
|
||||||
|
|
||||||
|
class MrNodeType(enum.Enum):
|
||||||
|
NONE = enum.auto()
|
||||||
|
BASENODE = enum.auto()
|
||||||
|
NODE = enum.auto()
|
||||||
|
COMMANDLINE = enum.auto()
|
||||||
|
INPUT = enum.auto()
|
||||||
|
|
||||||
def isNodeSaved(node):
|
def isNodeSaved(node):
|
||||||
"""Returns whether a node is identical to its serialized counterpart in the current graph file."""
|
"""Returns whether a node is identical to its serialized counterpart in the current graph file."""
|
||||||
filepath = node.graph.filepath
|
filepath = node.graph.filepath
|
||||||
|
@ -87,6 +94,9 @@ class BaseNode(object):
|
||||||
self.hasDynamicOutputAttribute = any(output.isDynamicValue for output in self.outputs)
|
self.hasDynamicOutputAttribute = any(output.isDynamicValue for output in self.outputs)
|
||||||
self.sourceCodeFolder = Path(getfile(self.__class__)).parent.resolve().as_posix()
|
self.sourceCodeFolder = Path(getfile(self.__class__)).parent.resolve().as_posix()
|
||||||
|
|
||||||
|
def getMrNodeType(self):
|
||||||
|
return MrNodeType.BASENODE
|
||||||
|
|
||||||
def upgradeAttributeValues(self, attrValues, fromVersion):
|
def upgradeAttributeValues(self, attrValues, fromVersion):
|
||||||
return attrValues
|
return attrValues
|
||||||
|
|
||||||
|
@ -236,6 +246,9 @@ class InputNode(BaseNode):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(InputNode, self).__init__()
|
super(InputNode, self).__init__()
|
||||||
|
|
||||||
|
def getMrNodeType(self):
|
||||||
|
return MrNodeType.INPUT
|
||||||
|
|
||||||
def processChunk(self, chunk):
|
def processChunk(self, chunk):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -245,6 +258,9 @@ class Node(BaseNode):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(Node, self).__init__()
|
super(Node, self).__init__()
|
||||||
|
|
||||||
|
def getMrNodeType(self):
|
||||||
|
return MrNodeType.NODE
|
||||||
|
|
||||||
def processChunkInEnvironment(self, chunk):
|
def processChunkInEnvironment(self, chunk):
|
||||||
if not isNodeSaved(chunk.node):
|
if not isNodeSaved(chunk.node):
|
||||||
raise RuntimeError("File must be saved before computing in isolated environment.")
|
raise RuntimeError("File must be saved before computing in isolated environment.")
|
||||||
|
@ -267,6 +283,9 @@ class CommandLineNode(BaseNode):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandLineNode, self).__init__()
|
super(CommandLineNode, self).__init__()
|
||||||
|
|
||||||
|
def getMrNodeType(self):
|
||||||
|
return MrNodeType.COMMANDLINE
|
||||||
|
|
||||||
def buildCommandLine(self, chunk):
|
def buildCommandLine(self, chunk):
|
||||||
|
|
||||||
cmdPrefix = ''
|
cmdPrefix = ''
|
||||||
|
|
|
@ -57,7 +57,6 @@ class Status(Enum):
|
||||||
class ExecMode(Enum):
|
class ExecMode(Enum):
|
||||||
NONE = auto()
|
NONE = auto()
|
||||||
LOCAL = auto()
|
LOCAL = auto()
|
||||||
LOCAL_ISOLATED = auto()
|
|
||||||
EXTERN = auto()
|
EXTERN = auto()
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,6 +85,7 @@ class StatusData(BaseObject):
|
||||||
self.nodeType: str = ""
|
self.nodeType: str = ""
|
||||||
self.packageName: str = ""
|
self.packageName: str = ""
|
||||||
self.packageVersion: str = ""
|
self.packageVersion: str = ""
|
||||||
|
self.mrNodeType: desc.MrNodeType = desc.MrNodeType.NONE
|
||||||
self.resetDynamicValues()
|
self.resetDynamicValues()
|
||||||
|
|
||||||
def resetDynamicValues(self):
|
def resetDynamicValues(self):
|
||||||
|
@ -108,6 +108,14 @@ class StatusData(BaseObject):
|
||||||
self.startDateTime = datetime.datetime.now().strftime(self.dateTimeFormatting)
|
self.startDateTime = datetime.datetime.now().strftime(self.dateTimeFormatting)
|
||||||
# to get datetime obj: datetime.datetime.strptime(obj, self.dateTimeFormatting)
|
# to get datetime obj: datetime.datetime.strptime(obj, self.dateTimeFormatting)
|
||||||
|
|
||||||
|
def initIsolatedCompute(self):
|
||||||
|
''' When submitting a node, we reset the status information to ensure that we do not keep outdated information.
|
||||||
|
'''
|
||||||
|
self.resetDynamicValues()
|
||||||
|
self.mrNodeType = desc.MrNodeType.NODE
|
||||||
|
self.sessionUid = None
|
||||||
|
self.submitterSessionUid = meshroom.core.sessionUid
|
||||||
|
|
||||||
def initSubmit(self):
|
def initSubmit(self):
|
||||||
''' When submitting a node, we reset the status information to ensure that we do not keep outdated information.
|
''' When submitting a node, we reset the status information to ensure that we do not keep outdated information.
|
||||||
'''
|
'''
|
||||||
|
@ -144,6 +152,10 @@ class StatusData(BaseObject):
|
||||||
self.execMode = d.get('execMode', ExecMode.NONE)
|
self.execMode = d.get('execMode', ExecMode.NONE)
|
||||||
if not isinstance(self.execMode, ExecMode):
|
if not isinstance(self.execMode, ExecMode):
|
||||||
self.execMode = ExecMode[self.execMode]
|
self.execMode = ExecMode[self.execMode]
|
||||||
|
self.mrNodeType = d.get('mrNodeType', desc.MrNodeType.NONE)
|
||||||
|
if not isinstance(self.mrNodeType, desc.MrNodeType):
|
||||||
|
self.mrNodeType = desc.MrNodeType[self.mrNodeType]
|
||||||
|
|
||||||
self.nodeName = d.get('nodeName', '')
|
self.nodeName = d.get('nodeName', '')
|
||||||
self.nodeType = d.get('nodeType', '')
|
self.nodeType = d.get('nodeType', '')
|
||||||
self.packageName = d.get('packageName', '')
|
self.packageName = d.get('packageName', '')
|
||||||
|
@ -456,8 +468,8 @@ class NodeChunk(BaseObject):
|
||||||
def _processInIsolatedEnvironment(self):
|
def _processInIsolatedEnvironment(self):
|
||||||
"""Process this node chunk in the isolated environment defined in the environment configuration."""
|
"""Process this node chunk in the isolated environment defined in the environment configuration."""
|
||||||
try:
|
try:
|
||||||
self._status.initSubmit()
|
self._status.initIsolatedCompute()
|
||||||
self.upgradeStatusTo(Status.RUNNING, execMode=ExecMode.LOCAL_ISOLATED)
|
self.upgradeStatusTo(Status.RUNNING, execMode=ExecMode.LOCAL)
|
||||||
self.node.nodeDesc.processChunkInEnvironment(self)
|
self.node.nodeDesc.processChunkInEnvironment(self)
|
||||||
except:
|
except:
|
||||||
# status should be already updated by meshroom_compute
|
# status should be already updated by meshroom_compute
|
||||||
|
@ -473,22 +485,25 @@ class NodeChunk(BaseObject):
|
||||||
self.node.updateOutputAttr()
|
self.node.updateOutputAttr()
|
||||||
|
|
||||||
def stopProcess(self):
|
def stopProcess(self):
|
||||||
if self.isExtern():
|
# if self.isExtern() or self._status.status != Status.RUNNING:
|
||||||
return
|
# return
|
||||||
if self._status.status != Status.RUNNING:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.upgradeStatusTo(Status.STOPPED)
|
self.upgradeStatusTo(Status.STOPPED)
|
||||||
self.node.nodeDesc.stopProcess(self)
|
self.node.nodeDesc.stopProcess(self)
|
||||||
|
|
||||||
def isExtern(self):
|
def isExtern(self):
|
||||||
""" The computation is managed externally by another instance of Meshroom, or by meshroom_compute on renderfarm).
|
""" The computation is managed externally by another instance of Meshroom.
|
||||||
In the ambiguous case of an isolated environment, it is considered as local as we can stop it.
|
In the ambiguous case of an isolated environment, it is considered as local as we can stop it.
|
||||||
"""
|
"""
|
||||||
if self._status.execMode == ExecMode.LOCAL_ISOLATED:
|
uid = self._status.submitterSessionUid if self._status.mrNodeType == desc.MrNodeType.NODE else meshroom.core.sessionUid
|
||||||
# It is a local isolated node, check if it is submitted by our current session.
|
return uid != meshroom.core.sessionUid
|
||||||
return self._status.submitterSessionUid != meshroom.core.sessionUid
|
|
||||||
return self._status.sessionUid != meshroom.core.sessionUid
|
# def isIndependantProcess(self):
|
||||||
|
# if self._status.execMode == ExecMode.EXTERN:
|
||||||
|
# # Compute is managed by another instance of Meshroom
|
||||||
|
# return True
|
||||||
|
# # Compute is using a meshroom_compute subprocess
|
||||||
|
# return self._status.mrNodeType == desc.MrNodeType.NODE
|
||||||
|
|
||||||
statusChanged = Signal()
|
statusChanged = Signal()
|
||||||
status = Property(Variant, lambda self: self._status, notify=statusChanged)
|
status = Property(Variant, lambda self: self._status, notify=statusChanged)
|
||||||
|
@ -1397,6 +1412,15 @@ class BaseNode(BaseObject):
|
||||||
if chunk.status.submitterSessionUid != meshroom.core.sessionUid:
|
if chunk.status.submitterSessionUid != meshroom.core.sessionUid:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def initFromThisSession(self):
|
||||||
|
if not self._chunks:
|
||||||
|
return False
|
||||||
|
for chunk in self._chunks:
|
||||||
|
uid = chunk.status.submitterSessionUid if chunk.status.mrNodeType == desc.MrNodeType.NODE else chunk.status.sessionUid
|
||||||
|
if uid != meshroom.core.sessionUid:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@Slot(result=bool)
|
@Slot(result=bool)
|
||||||
def canBeStopped(self):
|
def canBeStopped(self):
|
||||||
|
@ -1404,9 +1428,8 @@ class BaseNode(BaseObject):
|
||||||
# sessionUid as the Meshroom instance can be stopped
|
# sessionUid as the Meshroom instance can be stopped
|
||||||
# logging.warning(f"[{self.name}] canBeStopped: globalExecMode={self.globalExecMode} globalStatus={self.getGlobalStatus()} statusInThisSession={self.statusInThisSession()}, submitterStatusInThisSession={self.submitterStatusInThisSession()}")
|
# logging.warning(f"[{self.name}] canBeStopped: globalExecMode={self.globalExecMode} globalStatus={self.getGlobalStatus()} statusInThisSession={self.statusInThisSession()}, submitterStatusInThisSession={self.submitterStatusInThisSession()}")
|
||||||
return (self.getGlobalStatus() == Status.RUNNING and
|
return (self.getGlobalStatus() == Status.RUNNING and
|
||||||
((self.globalExecMode == ExecMode.LOCAL.name and self.statusInThisSession()) or
|
self.globalExecMode == ExecMode.LOCAL.name and
|
||||||
(self.globalExecMode == ExecMode.LOCAL_ISOLATED.name and self.submitterStatusInThisSession())
|
self.initFromThisSession())
|
||||||
))
|
|
||||||
|
|
||||||
@Slot(result=bool)
|
@Slot(result=bool)
|
||||||
def canBeCanceled(self):
|
def canBeCanceled(self):
|
||||||
|
@ -1414,9 +1437,8 @@ class BaseNode(BaseObject):
|
||||||
# sessionUid as the Meshroom instance can be canceled
|
# sessionUid as the Meshroom instance can be canceled
|
||||||
# logging.warning(f"[{self.name}] canBeCanceled: globalExecMode={self.globalExecMode} globalStatus={self.getGlobalStatus()} statusInThisSession={self.statusInThisSession()}, submitterStatusInThisSession={self.submitterStatusInThisSession()}")
|
# logging.warning(f"[{self.name}] canBeCanceled: globalExecMode={self.globalExecMode} globalStatus={self.getGlobalStatus()} statusInThisSession={self.statusInThisSession()}, submitterStatusInThisSession={self.submitterStatusInThisSession()}")
|
||||||
return (self.getGlobalStatus() == Status.SUBMITTED and
|
return (self.getGlobalStatus() == Status.SUBMITTED and
|
||||||
((self.globalExecMode == ExecMode.LOCAL.name and self.statusInThisSession()) or
|
self.globalExecMode == ExecMode.LOCAL.name and
|
||||||
(self.globalExecMode == ExecMode.LOCAL_ISOLATED.name and self.submitterStatusInThisSession())
|
self.initFromThisSession())
|
||||||
))
|
|
||||||
|
|
||||||
def hasImageOutputAttribute(self):
|
def hasImageOutputAttribute(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -30,7 +30,7 @@ from meshroom.core.graphIO import GraphIO
|
||||||
from meshroom.core.taskManager import TaskManager
|
from meshroom.core.taskManager import TaskManager
|
||||||
|
|
||||||
from meshroom.core.node import NodeChunk, Node, Status, ExecMode, CompatibilityNode, Position
|
from meshroom.core.node import NodeChunk, Node, Status, ExecMode, CompatibilityNode, Position
|
||||||
from meshroom.core import submitters
|
from meshroom.core import submitters, MrNodeType
|
||||||
from meshroom.ui import commands
|
from meshroom.ui import commands
|
||||||
from meshroom.ui.utils import makeProperty
|
from meshroom.ui.utils import makeProperty
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ class ChunksMonitor(QObject):
|
||||||
# when run locally, status changes are already notified.
|
# when run locally, status changes are already notified.
|
||||||
# Chunks with an ERROR status may be re-submitted externally and should thus still be monitored
|
# Chunks with an ERROR status may be re-submitted externally and should thus still be monitored
|
||||||
if (c.isExtern() and c._status.status in (Status.SUBMITTED, Status.RUNNING, Status.ERROR)) or (
|
if (c.isExtern() and c._status.status in (Status.SUBMITTED, Status.RUNNING, Status.ERROR)) or (
|
||||||
(c._status.execMode is ExecMode.LOCAL_ISOLATED) and (c._status.status in (Status.SUBMITTED, Status.RUNNING))):
|
(c._status.mrNodeType == MrNodeType.NODE) and (c._status.status in (Status.SUBMITTED, Status.RUNNING))):
|
||||||
files.append(c.statusFile)
|
files.append(c.statusFile)
|
||||||
chunks.append(c)
|
chunks.append(c)
|
||||||
return files, chunks
|
return files, chunks
|
||||||
|
@ -201,13 +201,14 @@ class ChunksMonitor(QObject):
|
||||||
times: the last modification times for currently monitored files.
|
times: the last modification times for currently monitored files.
|
||||||
"""
|
"""
|
||||||
newRecords = dict(zip(self.monitoredChunks, times))
|
newRecords = dict(zip(self.monitoredChunks, times))
|
||||||
hasChanges = False
|
hasChangesAndSuccess = False
|
||||||
for chunk, fileModTime in newRecords.items():
|
for chunk, fileModTime in newRecords.items():
|
||||||
# update chunk status if last modification time has changed since previous record
|
# update chunk status if last modification time has changed since previous record
|
||||||
if fileModTime != chunk.statusFileLastModTime:
|
if fileModTime != chunk.statusFileLastModTime:
|
||||||
chunk.updateStatusFromCache()
|
chunk.updateStatusFromCache()
|
||||||
hasChanges = True
|
if chunk.status.status == Status.SUCCESS:
|
||||||
if hasChanges:
|
hasChangesAndSuccess = True
|
||||||
|
if hasChangesAndSuccess:
|
||||||
chunk.node.loadOutputAttr()
|
chunk.node.loadOutputAttr()
|
||||||
|
|
||||||
def onFilePollerRefreshUpdated(self):
|
def onFilePollerRefreshUpdated(self):
|
||||||
|
@ -583,8 +584,7 @@ class UIGraph(QObject):
|
||||||
def updateGraphComputingStatus(self):
|
def updateGraphComputingStatus(self):
|
||||||
# update graph computing status
|
# update graph computing status
|
||||||
computingLocally = any([
|
computingLocally = any([
|
||||||
(((ch.status.execMode == ExecMode.LOCAL and ch.status.sessionUid == sessionUid) or
|
((ch.status.submitterSessionUid if ch.status.mrNodeType == MrNodeType.NODE else ch.status.sessionUid) == sessionUid) and (
|
||||||
ch.status.execMode == ExecMode.LOCAL_ISOLATED) and
|
|
||||||
ch.status.status in (Status.RUNNING, Status.SUBMITTED))
|
ch.status.status in (Status.RUNNING, Status.SUBMITTED))
|
||||||
for ch in self._sortedDFSChunks])
|
for ch in self._sortedDFSChunks])
|
||||||
submitted = any([ch.status.status == Status.SUBMITTED for ch in self._sortedDFSChunks])
|
submitted = any([ch.status.status == Status.SUBMITTED for ch in self._sortedDFSChunks])
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue