From 1e58747b765071a1a650ebadc5fe3a8c9291d7a7 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Wed, 25 Jun 2025 20:00:58 +0200 Subject: [PATCH] Add concurrency locks and detailed logging to canvas processing Introduces concurrency locks in Python and JavaScript to prevent simultaneous processing and saving operations in canvas-related workflows. Adds extensive logging throughout the canvas image processing, saving, and matting routines to aid debugging and trace execution flow. Also improves error handling and state management in both backend and frontend code. --- canvas_node.py | 67 ++++++++++++++++++++++++++++++++++++++--- js/Canvas.js | 77 ++++++++++++++++++++++++++++++++++++++++++----- js/Canvas_view.js | 45 ++++++++++++++++++++++----- 3 files changed, 169 insertions(+), 20 deletions(-) diff --git a/canvas_node.py b/canvas_node.py index e3af507..2b861fc 100644 --- a/canvas_node.py +++ b/canvas_node.py @@ -190,18 +190,35 @@ class CanvasNode: print(f"Error in add_mask_to_canvas: {str(e)}") return None + # Zmienna blokująca równoczesne wykonania + _processing_lock = None + def process_canvas_image(self, canvas_image, trigger, output_switch, cache_enabled, input_image=None, input_mask=None): try: + # Sprawdź czy już trwa przetwarzanie + if self.__class__._processing_lock is not None: + print(f"[OUTPUT_LOG] Process already in progress, waiting for completion...") + return () # Zwróć pusty wynik, aby uniknąć równoczesnych przetworzeń + + # Ustaw blokadę + self.__class__._processing_lock = True + current_execution = self.get_execution_id() - print(f"Processing canvas image, execution ID: {current_execution}") + print(f"[OUTPUT_LOG] Starting process_canvas_image - execution ID: {current_execution}, trigger: {trigger}") + print(f"[OUTPUT_LOG] Canvas image filename: {canvas_image}") + print(f"[OUTPUT_LOG] Output switch: {output_switch}, Cache enabled: {cache_enabled}") + print(f"[OUTPUT_LOG] Input image provided: {input_image is not None}") + print(f"[OUTPUT_LOG] Input mask provided: {input_mask is not None}") if current_execution != self.__class__._canvas_cache['last_execution_id']: - print(f"New execution detected: {current_execution}") + print(f"[OUTPUT_LOG] New execution detected: {current_execution} (previous: {self.__class__._canvas_cache['last_execution_id']})") self.__class__._canvas_cache['image'] = None self.__class__._canvas_cache['mask'] = None self.__class__._canvas_cache['last_execution_id'] = current_execution + else: + print(f"[OUTPUT_LOG] Same execution ID, using cached data") if input_image is not None: print("Input image received, converting to PIL Image...") @@ -253,6 +270,7 @@ class CanvasNode: # Wczytaj obraz bez maski path_image_without_mask = folder_paths.get_annotated_filepath( canvas_image.replace('.png', '_without_mask.png')) + print(f"[OUTPUT_LOG] Loading image without mask from: {path_image_without_mask}") i = Image.open(path_image_without_mask) i = ImageOps.exif_transpose(i) if i.mode not in ['RGB', 'RGBA']: @@ -263,37 +281,56 @@ class CanvasNode: alpha = image[..., 3:] image = rgb * alpha + (1 - alpha) * 0.5 processed_image = torch.from_numpy(image)[None,] + print(f"[OUTPUT_LOG] Successfully loaded image without mask, shape: {processed_image.shape}") except Exception as e: - print(f"Error loading image without mask: {str(e)}") + print(f"[OUTPUT_LOG] Error loading image without mask: {str(e)}") processed_image = torch.ones((1, 512, 512, 3), dtype=torch.float32) + print(f"[OUTPUT_LOG] Using default image, shape: {processed_image.shape}") try: # Wczytaj maskę path_image = folder_paths.get_annotated_filepath(canvas_image) path_mask = path_image.replace('.png', '_mask.png') + print(f"[OUTPUT_LOG] Looking for mask at: {path_mask}") if os.path.exists(path_mask): + print(f"[OUTPUT_LOG] Mask file exists, loading...") mask = Image.open(path_mask).convert('L') mask = np.array(mask).astype(np.float32) / 255.0 processed_mask = torch.from_numpy(mask)[None,] + print(f"[OUTPUT_LOG] Successfully loaded mask, shape: {processed_mask.shape}") else: + print(f"[OUTPUT_LOG] Mask file does not exist, creating default mask") processed_mask = torch.ones((1, processed_image.shape[1], processed_image.shape[2]), dtype=torch.float32) + print(f"[OUTPUT_LOG] Default mask created, shape: {processed_mask.shape}") except Exception as e: - print(f"Error loading mask: {str(e)}") + print(f"[OUTPUT_LOG] Error loading mask: {str(e)}") processed_mask = torch.ones((1, processed_image.shape[1], processed_image.shape[2]), dtype=torch.float32) + print(f"[OUTPUT_LOG] Fallback mask created, shape: {processed_mask.shape}") if not output_switch: + print(f"[OUTPUT_LOG] Output switch is OFF, returning empty tuple") return () + print(f"[OUTPUT_LOG] About to return output - Image shape: {processed_image.shape}, Mask shape: {processed_mask.shape}") + print(f"[OUTPUT_LOG] Image tensor info - dtype: {processed_image.dtype}, device: {processed_image.device}") + print(f"[OUTPUT_LOG] Mask tensor info - dtype: {processed_mask.dtype}, device: {processed_mask.device}") + self.update_persistent_cache() - + + print(f"[OUTPUT_LOG] Successfully returning processed image and mask") return (processed_image, processed_mask) except Exception as e: print(f"Error in process_canvas_image: {str(e)}") traceback.print_exc() return () + + finally: + # Zwolnij blokadę + self.__class__._processing_lock = None + print(f"[OUTPUT_LOG] Process completed, lock released") def get_cached_data(self): return { @@ -572,8 +609,24 @@ class BiRefNetMatting: return m.hexdigest() +# Zmienna blokująca równoczesne wywołania matting +_matting_lock = None + @PromptServer.instance.routes.post("/matting") async def matting(request): + global _matting_lock + + # Sprawdź czy już trwa przetwarzanie + if _matting_lock is not None: + print("Matting already in progress, rejecting request") + return web.json_response({ + "error": "Another matting operation is in progress", + "details": "Please wait for the current operation to complete" + }, status=429) # 429 Too Many Requests + + # Ustaw blokadę + _matting_lock = True + try: print("Received matting request") data = await request.json() @@ -606,6 +659,10 @@ async def matting(request): "error": str(e), "details": traceback.format_exc() }, status=500) + finally: + # Zwolnij blokadę + _matting_lock = None + print("Matting lock released") def convert_base64_to_tensor(base64_str): diff --git a/js/Canvas.js b/js/Canvas.js index a68055a..9ff7791 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -99,12 +99,29 @@ export class Canvas { } async loadStateFromDB() { + // Sprawdź czy już trwa ładowanie + if (this._loadInProgress) { + console.log("Load already in progress, waiting..."); + return this._loadInProgress; + } + console.log("Attempting to load state from IndexedDB for node:", this.node.id); if (!this.node.id) { console.error("Node ID is not available for loading state from DB."); return false; } + this._loadInProgress = this._performLoad(); + + try { + const result = await this._loadInProgress; + return result; + } finally { + this._loadInProgress = null; + } + } + + async _performLoad() { try { const savedState = await getCanvasState(this.node.id); if (!savedState) { @@ -1713,6 +1730,28 @@ export class Canvas { async saveToServer(fileName) { + // Sprawdź czy już trwa zapis + if (this._saveInProgress) { + console.log(`[CANVAS_OUTPUT_LOG] Save already in progress, waiting...`); + return this._saveInProgress; + } + + console.log(`[CANVAS_OUTPUT_LOG] Starting saveToServer with fileName: ${fileName}`); + console.log(`[CANVAS_OUTPUT_LOG] Canvas dimensions: ${this.width}x${this.height}`); + console.log(`[CANVAS_OUTPUT_LOG] Number of layers: ${this.layers.length}`); + + // Utwórz Promise dla aktualnego zapisu + this._saveInProgress = this._performSave(fileName); + + try { + const result = await this._saveInProgress; + return result; + } finally { + this._saveInProgress = null; + } + } + + async _performSave(fileName) { // Zapisz stan do IndexedDB przed zapisem na serwer await this.saveStateToDB(true); @@ -1732,9 +1771,17 @@ export class Canvas { maskCtx.fillStyle = '#ffffff'; // Białe tło dla wolnych przestrzeni maskCtx.fillRect(0, 0, this.width, this.height); + + console.log(`[CANVAS_OUTPUT_LOG] Canvas contexts created, starting layer rendering`); // Rysowanie warstw - this.layers.sort((a, b) => a.zIndex - b.zIndex).forEach(layer => { + const sortedLayers = this.layers.sort((a, b) => a.zIndex - b.zIndex); + console.log(`[CANVAS_OUTPUT_LOG] Processing ${sortedLayers.length} layers in order`); + + sortedLayers.forEach((layer, index) => { + console.log(`[CANVAS_OUTPUT_LOG] Processing layer ${index}: zIndex=${layer.zIndex}, size=${layer.width}x${layer.height}, pos=(${layer.x},${layer.y})`); + console.log(`[CANVAS_OUTPUT_LOG] Layer ${index}: blendMode=${layer.blendMode || 'normal'}, opacity=${layer.opacity !== undefined ? layer.opacity : 1}`); + tempCtx.save(); tempCtx.globalCompositeOperation = layer.blendMode || 'normal'; tempCtx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1; @@ -1742,6 +1789,8 @@ export class Canvas { tempCtx.rotate(layer.rotation * Math.PI / 180); tempCtx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); tempCtx.restore(); + + console.log(`[CANVAS_OUTPUT_LOG] Layer ${index} rendered successfully`); maskCtx.save(); maskCtx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); @@ -1826,23 +1875,29 @@ export class Canvas { // Zapisz obraz bez maski const fileNameWithoutMask = fileName.replace('.png', '_without_mask.png'); + console.log(`[CANVAS_OUTPUT_LOG] Saving image without mask as: ${fileNameWithoutMask}`); + tempCanvas.toBlob(async (blobWithoutMask) => { + console.log(`[CANVAS_OUTPUT_LOG] Created blob for image without mask, size: ${blobWithoutMask.size} bytes`); const formDataWithoutMask = new FormData(); formDataWithoutMask.append("image", blobWithoutMask, fileNameWithoutMask); formDataWithoutMask.append("overwrite", "true"); try { - await fetch("/upload/image", { + const response = await fetch("/upload/image", { method: "POST", body: formDataWithoutMask, }); + console.log(`[CANVAS_OUTPUT_LOG] Image without mask upload response: ${response.status}`); } catch (error) { - console.error("Error uploading image without mask:", error); + console.error(`[CANVAS_OUTPUT_LOG] Error uploading image without mask:`, error); } }, "image/png"); // Zapisz obraz z maską + console.log(`[CANVAS_OUTPUT_LOG] Saving main image as: ${fileName}`); tempCanvas.toBlob(async (blob) => { + console.log(`[CANVAS_OUTPUT_LOG] Created blob for main image, size: ${blob.size} bytes`); const formData = new FormData(); formData.append("image", blob, fileName); formData.append("overwrite", "true"); @@ -1852,11 +1907,15 @@ export class Canvas { method: "POST", body: formData, }); + console.log(`[CANVAS_OUTPUT_LOG] Main image upload response: ${resp.status}`); if (resp.status === 200) { + const maskFileName = fileName.replace('.png', '_mask.png'); + console.log(`[CANVAS_OUTPUT_LOG] Saving mask as: ${maskFileName}`); + maskCanvas.toBlob(async (maskBlob) => { + console.log(`[CANVAS_OUTPUT_LOG] Created blob for mask, size: ${maskBlob.size} bytes`); const maskFormData = new FormData(); - const maskFileName = fileName.replace('.png', '_mask.png'); maskFormData.append("image", maskBlob, maskFileName); maskFormData.append("overwrite", "true"); @@ -1865,26 +1924,28 @@ export class Canvas { method: "POST", body: maskFormData, }); + console.log(`[CANVAS_OUTPUT_LOG] Mask upload response: ${maskResp.status}`); if (maskResp.status === 200) { const data = await resp.json(); this.widget.value = data.name; + console.log(`[CANVAS_OUTPUT_LOG] All files saved successfully, widget value set to: ${data.name}`); resolve(true); } else { - console.error("Error saving mask: " + maskResp.status); + console.error(`[CANVAS_OUTPUT_LOG] Error saving mask: ${maskResp.status}`); resolve(false); } } catch (error) { - console.error("Error saving mask:", error); + console.error(`[CANVAS_OUTPUT_LOG] Error saving mask:`, error); resolve(false); } }, "image/png"); } else { - console.error(resp.status + " - " + resp.statusText); + console.error(`[CANVAS_OUTPUT_LOG] Main image upload failed: ${resp.status} - ${resp.statusText}`); resolve(false); } } catch (error) { - console.error(error); + console.error(`[CANVAS_OUTPUT_LOG] Error uploading main image:`, error); resolve(false); } }, "image/png"); diff --git a/js/Canvas_view.js b/js/Canvas_view.js index cf2023d..b29ef34 100644 --- a/js/Canvas_view.js +++ b/js/Canvas_view.js @@ -906,22 +906,53 @@ async function createCanvasWidget(node, widget, app) { } }; + // Zmienna do śledzenia czy wykonanie jest w trakcie + let executionInProgress = false; + api.addEventListener("execution_start", async () => { + console.log(`[CANVAS_VIEW_LOG] Execution start event for node ${node.id}`); + console.log(`[CANVAS_VIEW_LOG] Widget value: ${widget.value}`); + console.log(`[CANVAS_VIEW_LOG] Node inputs: ${node.inputs?.length || 0}`); + + // Sprawdź czy już trwa wykonanie + if (executionInProgress) { + console.log(`[CANVAS_VIEW_LOG] Execution already in progress, skipping...`); + return; + } + + // Ustaw flagę wykonania + executionInProgress = true; + + try { + await canvas.saveToServer(widget.value); + console.log(`[CANVAS_VIEW_LOG] Canvas saved to server`); - await canvas.saveToServer(widget.value); - - if (node.inputs[0].link) { - const linkId = node.inputs[0].link; - const inputData = app.nodeOutputs[linkId]; - if (inputData) { - imageCache.set(linkId, inputData); + if (node.inputs[0]?.link) { + const linkId = node.inputs[0].link; + const inputData = app.nodeOutputs[linkId]; + console.log(`[CANVAS_VIEW_LOG] Input link ${linkId} has data: ${!!inputData}`); + if (inputData) { + imageCache.set(linkId, inputData); + console.log(`[CANVAS_VIEW_LOG] Input data cached for link ${linkId}`); + } + } else { + console.log(`[CANVAS_VIEW_LOG] No input link found`); } + } catch (error) { + console.error(`[CANVAS_VIEW_LOG] Error during execution:`, error); + } finally { + // Zwolnij flagę wykonania + executionInProgress = false; + console.log(`[CANVAS_VIEW_LOG] Execution completed, flag released`); } }); const originalSaveToServer = canvas.saveToServer; canvas.saveToServer = async function (fileName) { + console.log(`[CANVAS_VIEW_LOG] saveToServer called with fileName: ${fileName}`); + console.log(`[CANVAS_VIEW_LOG] Current execution context - node ID: ${node.id}`); const result = await originalSaveToServer.call(this, fileName); + console.log(`[CANVAS_VIEW_LOG] saveToServer completed, result: ${result}`); return result; };