Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ gui-js/documentation/*
gui-js/dist/*
gui-js/.nx/*
gui-js/.angular/*
doc/minsky/*
doc/minsky/*
# CodeQL temporary files
_codeql_detected_source_root
30 changes: 21 additions & 9 deletions RESTService/addon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,10 @@ namespace minsky
{
// ensure access to only one global Minsky object at a time,
// particular needed for jest tests, which run in parallel
mutex minskyCmdMutex;
// Use recursive_mutex to allow the same thread to acquire it multiple times
// (e.g., JS thread may hold it in doCommand() and then macOSXDrawNativeWindows()
// may also be scheduled on the same thread)
recursive_mutex minskyCmdMutex;

struct AddOnMinsky: public RESTMinsky
{
Expand Down Expand Up @@ -235,7 +238,7 @@ namespace minsky

string doCommand(const string& command, const json_pack_t& arguments)
{
const lock_guard<mutex> lock(minskyCmdMutex);
const lock_guard<recursive_mutex> lock(minskyCmdMutex);
const Timer timer(timers[command]);

// if reset requested, postpone it
Expand All @@ -250,7 +253,7 @@ namespace minsky

void drawNativeWindows()
{
const lock_guard<mutex> lock(minskyCmdMutex);
const lock_guard<recursive_mutex> lock(minskyCmdMutex);
const Timer timer(timers["draw"]);
for (auto i: nativeWindowsToRedraw)
try
Expand All @@ -276,19 +279,28 @@ namespace minsky

void macOSXDrawNativeWindows()
{
// share the lock with all window redraw routines - when all windows redrawn, lock is released
auto lock=make_shared<lock_guard<mutex>>(minskyCmdMutex);
// Don't hold the lock here - pass mutex reference to windows so drawRect can lock when called
const Timer timer(timers["draw"]);
for (auto i: nativeWindowsToRedraw)
macOSXRedraw(*i,lock);
decltype(nativeWindowsToRedraw) windows;
{
const lock_guard<recursive_mutex> lock(minskyCmdMutex);
windows.swap(nativeWindowsToRedraw);
}
for (auto i: windows)
macOSXRedraw(*i,minskyCmdMutex);
nativeWindowsToRedraw.clear();
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to nativeWindowsToRedraw.clear() on line 291 is redundant and potentially incorrect. Line 287 already moves the contents of nativeWindowsToRedraw into the local windows variable via swap(), leaving nativeWindowsToRedraw empty. This clear operation has no effect. If new windows are added to nativeWindowsToRedraw between the swap (line 287) and this clear (line 291), they would be incorrectly discarded.

Suggested change
nativeWindowsToRedraw.clear();

Copilot uses AI. Check for mistakes.
drawLaunched=false;
}

void macOSXLaunchDrawNativeWindows()
{
drawLaunched=true;
tsDrawNativeWindows_.BlockingCall(this);
// Use NonBlockingCall instead of BlockingCall to prevent deadlock.
// BlockingCall would block the Minsky thread waiting for JS thread to complete,
// but JS thread needs to acquire minskyCmdMutex which could be held, causing deadlock.
// NonBlockingCall allows Minsky thread to continue, and JS thread will acquire
// the mutex when it's ready to run macOSXDrawNativeWindows().
tsDrawNativeWindows_.NonBlockingCall(this);
}

void run()
Expand All @@ -313,7 +325,7 @@ namespace minsky
if (reset_flag() && resetDuration<resetPostponedThreshold && resetAt<std::chrono::system_clock::now())
try
{
const lock_guard<mutex> lock(minskyCmdMutex);
const lock_guard<recursive_mutex> lock(minskyCmdMutex);
if (reset_flag()) // check again, in case another thread got there first
{
const Timer timer(timers["minsky.reset"]);
Expand Down
2 changes: 1 addition & 1 deletion doc/Ravel.tex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%% LyX 2.3.7 created this file. For more info, see http://www.lyx.org/.
%% LyX 2.3.7 created this file. For more info, see http://www.lyx.org/.
%% Do not edit unless you really know what you are doing.
\documentclass{book}
\usepackage[latin9]{inputenc}
Expand Down
10 changes: 9 additions & 1 deletion model/getContext.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <cairo/cairo-quartz.h>
#include <iostream>
#include <exception>
#include <mutex>
#include "minsky_epilogue.h"

using namespace std;
Expand Down Expand Up @@ -100,13 +101,20 @@ namespace minsky
void NSContext::requestRedraw()
{
[impl->cairoView setNeedsDisplay: true];
// Force the view to display immediately instead of waiting for the next event loop
[impl->cairoView displayIfNeeded];
}

}

@implementation CairoView
-(void) drawRect: (NSRect)rect
{
// Lock the mutex when actually drawing, not before
std::optional<std::lock_guard<std::recursive_mutex>> lock;
if (winfo->cmdMutex)
lock.emplace(*winfo->cmdMutex);
Comment on lines +113 to +116
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional locking pattern here is fragile. If cmdMutex is null, drawing proceeds without any synchronization, which could lead to race conditions. Consider documenting why it's safe to draw without a lock when cmdMutex is null, or ensure cmdMutex is always initialized to prevent subtle concurrency bugs.

Copilot uses AI. Check for mistakes.

auto context = [[NSGraphicsContext currentContext] CGContext];
auto frame=[self frame];
CGContextTranslateCTM(context,winfo->offsetLeft,winfo->childHeight+(winfo->hasScrollBars?20:0));
Expand All @@ -116,7 +124,7 @@ namespace minsky
cairo_surface_set_device_offset(winfo->bufferSurface->surface(), 0, 20);
winfo->draw();
winfo->bufferSurface.reset();
winfo->lock.reset(); // unlock any mutex attached to this window
// lock will be automatically released when it goes out of scope
}
- (NSView *) hitTest: (NSPoint) aPoint
{
Expand Down
4 changes: 2 additions & 2 deletions model/renderNativeWindow.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ namespace minsky
winInfoPtr.reset();
}

void macOSXRedraw(RenderNativeWindow& window,const std::shared_ptr<std::lock_guard<std::mutex>>& lock)
void macOSXRedraw(RenderNativeWindow& window,std::recursive_mutex& cmdMutex)
{
#ifdef MAC_OSX_TK
if (!window.winInfoPtr.get()) return;
window.winInfoPtr->lock=lock;
window.winInfoPtr->cmdMutex=&cmdMutex;
window.winInfoPtr->requestRedraw();
#endif
}
Expand Down
12 changes: 7 additions & 5 deletions model/renderNativeWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
#include <cairoSurfaceImage.h>
#include <plot.h>

#ifndef CLASSDESC_TYPENAME___std__lock_guard__mutex___
#define CLASSDESC_TYPENAME___std__lock_guard__mutex___
#include <mutex>

#ifndef CLASSDESC_TYPENAME___std__lock_guard__recursive_mutex___
#define CLASSDESC_TYPENAME___std__lock_guard__recursive_mutex___
namespace classdesc
{
template <> struct tn<std::lock_guard<std::mutex>>
template <> struct tn<std::lock_guard<std::recursive_mutex>>
{
static string name() {return "std::lock_guard<std::mutex>";}
static string name() {return "std::lock_guard<std::recursive_mutex>";}
};
}
#endif
Expand Down Expand Up @@ -65,7 +67,7 @@ namespace minsky
void draw();
void requestRedraw();
// implemented as a free function to avoid Classdesc exposing this to Typescript
friend void macOSXRedraw(RenderNativeWindow&,const std::shared_ptr<std::lock_guard<std::mutex>>&);
friend void macOSXRedraw(RenderNativeWindow&,std::recursive_mutex&);
// do not clobber winInfoPtr on load of model
RenderNativeWindow& operator=(const RenderNativeWindow& x) {ecolab::CairoSurface::operator=(x); return *this;}
RenderNativeWindow()=default;
Expand Down
4 changes: 2 additions & 2 deletions model/windowInformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ namespace minsky
HANDLE hOld; //
#elif defined(MAC_OSX_TK)
NSContext nsContext;
std::shared_ptr<std::lock_guard<std::mutex>> lock;
Winfo(NSContext&& nsContext): nsContext(std::move(nsContext)) {}
std::recursive_mutex* cmdMutex; // pointer to minskyCmdMutex for locking during draw
Winfo(NSContext&& nsContext): nsContext(std::move(nsContext)), cmdMutex(nullptr) {}
#elif defined(USE_X11)
Window parentWindowId;
Window childWindowId, bufferWindowId;
Expand Down