Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suspend core by default when calling plugin command functions #5112

Merged
merged 7 commits into from
Dec 21, 2024
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
3 changes: 3 additions & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ Template for new versions:

## Removed

## Internals
- Plugin command callbacks are now called with the core suspended by default so DF memory is always safe to access

# 50.15-r1.2

## Misc Improvements
Expand Down
2 changes: 1 addition & 1 deletion library/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2416,7 +2416,7 @@ int Core::Shutdown ( void )
d->hotkeythread.join();
d->iothread.join();

CoreSuspendClaimer suspend;
Copy link
Member

@ab9rf ab9rf Dec 21, 2024

Choose a reason for hiding this comment

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

This shouldn't be here at all. This is in Core::Shutdown, the core suspension mechanism has already been dismantled at this point and all DFHack threads, as well as the DF simulation thread, have been terminated. The suspender should be removed entirely since there's nothing to protect against here.

This usage came up when I added instrumentation to detect attempts to acquire a CoreSuspender from the DF render thread, because doing this is unsafe. It happens that DF calls dfhooks_shutdown from the render (main) thread, so the attempt to acquire a CoreSuspender here set off the instrumentation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in ebac377

CoreSuspender suspend;
if(plug_mgr)
{
delete plug_mgr;
Expand Down
52 changes: 20 additions & 32 deletions library/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,43 +469,31 @@ bool Plugin::reload(color_ostream &out)

command_result Plugin::invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters)
{
Core & c = Core::getInstance();
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED)
{
for (size_t i = 0; i < commands.size();i++)
if (state == PS_LOADED) {
if (auto cmdIt = std::ranges::find_if(commands, [&](const PluginCommand &cmd) { return cmd.name == command; });
commands.end() != cmdIt)
{
PluginCommand &cmd = commands[i];
if(cmd.name == command)
{
// running interactive things from some other source than the console would break it
if(!out.is_console() && cmd.interactive)
cr = CR_NEEDS_CONSOLE;
else if (cmd.guard)
{
// Execute hotkey commands in a way where they can
// expect their guard conditions to be matched,
// so as to avoid duplicating checks.
// This means suspending the core beforehand.
CoreSuspender suspend(&c);
df::viewscreen *top = c.getTopViewscreen();

if (!cmd.guard(top))
{
out.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str());
cr = CR_WRONG_USAGE;
}
else
{
cr = cmd.function(out, parameters);
}
// running interactive things from some other source than the console would break it
if (!out.is_console() && cmdIt->interactive)
cr = CR_NEEDS_CONSOLE;
else if (cmdIt->guard) {
CoreSuspender suspend;
if (!cmdIt->guard(Core::getInstance().getTopViewscreen())) {
out.printerr("Could not invoke %s: unsuitable UI state.\n", command.c_str());
cr = CR_WRONG_USAGE;
}
else
{
cr = cmd.function(out, parameters);
else {
cr = cmdIt->function(out, parameters);
}
break;
}
else if (cmdIt->unlocked) {
cr = cmdIt->function(out, parameters);
}
else {
CoreSuspender suspend;
cr = cmdIt->function(out, parameters);
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions library/include/Core.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,4 @@ namespace DFHack
CoreSuspendClaimMain();
~CoreSuspendClaimMain();
};

using CoreSuspendClaimer = CoreSuspender;
}
24 changes: 13 additions & 11 deletions library/include/PluginManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,12 @@ namespace DFHack
const char * _description,
command_function function_,
bool interactive_ = false,
bool unlocked_ = false,
const char * usage_ = ""
)
: name(_name), description(_description),
function(function_), interactive(interactive_),
guard(NULL), usage(usage_)
: name{_name}, description{_description}, function{function_},
interactive{interactive_}, unlocked{unlocked_}, guard{NULL},
usage{usage_}
{
fix_usage();
}
Expand All @@ -113,9 +114,9 @@ namespace DFHack
command_function function_,
command_hotkey_guard guard_,
const char * usage_ = "")
: name(_name), description(_description),
function(function_), interactive(false),
guard(guard_), usage(usage_)
: name{_name}, description{_description}, function{function_},
interactive{false}, unlocked{false}, guard{guard_},
usage{usage_}
{
fix_usage();
}
Expand All @@ -128,11 +129,12 @@ namespace DFHack

bool isHotkeyCommand() const { return guard != NULL; }

std::string name;
std::string description;
command_function function;
bool interactive;
command_hotkey_guard guard;
const std::string name;
const std::string description;
const command_function function;
const bool interactive;
const bool unlocked;
const command_hotkey_guard guard;
std::string usage;
};
class Plugin
Expand Down
3 changes: 1 addition & 2 deletions library/include/VTableInterpose.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ namespace DFHack

DEFINE_VMETHOD_INTERPOSE(int, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
// CoreSuspender suspend;
...
... this->field ... // access fields of the df::someclass object
...
Expand Down
2 changes: 1 addition & 1 deletion library/modules/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ df::viewscreen *dfhack_lua_viewscreen::get_pointer(lua_State *L, int idx, bool m

bool dfhack_lua_viewscreen::safe_call_lua(int (*pf)(lua_State *), int args, int rvs)
{
CoreSuspendClaimer suspend;
CoreSuspender suspend;
color_ostream_proxy out(Core::getInstance().getConsole());

auto L = Lua::Core::State;
Expand Down
2 changes: 0 additions & 2 deletions plugins/3dveins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1627,8 +1627,6 @@ command_result cmd_3dveins(color_ostream &con, std::vector<std::string> & parame
return CR_WRONG_USAGE;
}

CoreSuspender suspend;

if (!Maps::IsValid())
{
con.printerr("Map is not available!\n");
Expand Down
2 changes: 0 additions & 2 deletions plugins/aquifer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
}

static command_result do_command(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded()) {
out.printerr("Cannot run %s without a loaded map.\n", plugin_name);
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/autobutcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,6 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op
static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts);

static command_result df_autobutcher(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/autochop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) {
}

static command_result do_command(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
4 changes: 1 addition & 3 deletions plugins/autoclothing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,7 @@ static bool validateMaterialCategory(ClothingRequirement *requirement) {

// A command! It sits around and looks pretty. And it's nice and friendly.
command_result autoclothing(color_ostream &out, vector<string> &parameters)
{ // Be sure to suspend the core if any DF state is read or modified
CoreSuspender suspend;

{
if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
4 changes: 0 additions & 4 deletions plugins/autodump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ static command_result autodump_main(color_ostream &out, vector<string> &paramete
}

command_result df_autodump(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;
return autodump_main(out, parameters);
}

Expand All @@ -201,7 +200,6 @@ command_result df_autodump_destroy_here(color_ostream &out, vector<string> &para
vector<string> args;
args.push_back("destroy-here");

CoreSuspender suspend;
return autodump_main(out, args);
}

Expand All @@ -213,8 +211,6 @@ command_result df_autodump_destroy_item(color_ostream &out, vector<string> &para
if (!parameters.empty())
return CR_WRONG_USAGE;

CoreSuspender suspend;

df::item *item = Gui::getSelectedItem(out);
if (!item)
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/autofarm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,6 @@ static command_result autofarm(color_ostream& out, std::vector<std::string>& par
return CR_FAILURE;
}

CoreSuspender suspend;

if (parameters.size() == 1 && parameters[0] == "runonce")
{
if (autofarmInstance) autofarmInstance->process(out);
Expand Down
2 changes: 0 additions & 2 deletions plugins/autolabor/autolabor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1094,8 +1094,6 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable )

command_result autolabor (color_ostream &out, std::vector <std::string> & parameters)
{
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/autolabor/labormanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1977,8 +1977,6 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)

command_result labormanager(color_ostream &out, std::vector <std::string> & parameters)
{
CoreSuspender suspend;

if (!Core::getInstance().isWorldLoaded()) {
out.printerr("World is not loaded: please load a game first.\n");
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/autonestbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ static const struct_field_info autonestbox_options_fields[] = {
struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn<autonestbox_options>, NULL, "autonestbox_options", NULL, autonestbox_options_fields);

static command_result df_autonestbox(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/blueprint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1591,8 +1591,6 @@ static bool do_transform(color_ostream &out,
static command_result do_blueprint(color_ostream &out,
const vector<string> &parameters,
vector<string> &files) {
CoreSuspender suspend;

if (parameters.size() >= 1 && parameters[0] == "gui") {
ostringstream command;
command << "gui/blueprint";
Expand Down
2 changes: 1 addition & 1 deletion plugins/building-hacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ struct work_hook : df::building_workshopst{
{
if(world->frame_counter % def->skip_updates == 0)
{
CoreSuspendClaimer suspend;
CoreSuspender suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
onUpdateAction(out,this);
}
Expand Down
2 changes: 0 additions & 2 deletions plugins/buildingplan/buildingplan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) {
}

static command_result do_command(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot configure %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/burrow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
}

static command_result do_command(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;

if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) {
out.printerr("Cannot run %s without a loaded fort.\n", plugin_name);
return CR_FAILURE;
Expand Down
2 changes: 0 additions & 2 deletions plugins/changeitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ command_result changeitem_execute(

command_result df_changeitem(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;

bool here = false;
bool info = false;
bool force = false;
Expand Down
2 changes: 0 additions & 2 deletions plugins/changelayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ static bool warned = false;

command_result changelayer (color_ostream &out, std::vector <std::string> & parameters)
{
CoreSuspender suspend;

string material;
bool force = false;
bool all_biomes = false;
Expand Down
2 changes: 0 additions & 2 deletions plugins/changevein.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,6 @@ command_result df_changevein (color_ostream &out, vector <string> & parameters)
if (parameters.size() != 1)
return CR_WRONG_USAGE;

CoreSuspender suspend;

if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
Expand Down
2 changes: 0 additions & 2 deletions plugins/channel-safely/channel-safely-plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ namespace CSP {
}

void UnpauseEvent(bool full_scan = false){
CoreSuspender suspend; // we need exclusive access to df memory and this call stack doesn't already have a lock
DEBUG(plugin).print("UnpauseEvent()\n");
ChannelManager::Get().build_groups(full_scan);
ChannelManager::Get().manage_groups();
Expand Down Expand Up @@ -361,7 +360,6 @@ namespace CSP {
}

void OnUpdate(color_ostream &out) {
CoreSuspender suspend;
if (World::ReadPauseState())
return;

Expand Down
2 changes: 0 additions & 2 deletions plugins/cleanconst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ REQUIRE_GLOBAL(world);

command_result df_cleanconst(color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;

if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
Expand Down
3 changes: 0 additions & 3 deletions plugins/cleaners.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ command_result cleanplants (color_ostream &out)

command_result spotclean (color_ostream &out, vector <string> & parameters)
{
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
if (cursor->x < 0)
{
out.printerr("The cursor is not active.\n");
Expand Down Expand Up @@ -236,8 +235,6 @@ command_result clean (color_ostream &out, vector <string> & parameters)
if(!map && !units && !items && !plants)
return CR_WRONG_USAGE;

CoreSuspender suspend;

if(map)
cleanmap(out,snow,mud,item_spatter);
if(units)
Expand Down
2 changes: 0 additions & 2 deletions plugins/cleanowned.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ command_result df_cleanowned (color_ostream &out, vector <string> & parameters)
return CR_WRONG_USAGE;
}

CoreSuspender suspend;

if (!Translation::IsValid())
{
out.printerr("Translation data unavailable!\n");
Expand Down
2 changes: 0 additions & 2 deletions plugins/createitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ command_result df_createitem (color_ostream &out, vector<string> &parameters) {

if (parameters.size() == 1) {
if (parameters[0] == "inspect") {
CoreSuspender suspend;
auto item = Gui::getSelectedItem(out);
if (!item)
return CR_FAILURE;
Expand Down Expand Up @@ -394,7 +393,6 @@ command_result df_createitem (color_ostream &out, vector<string> &parameters) {
out.printerr("Map is not available.\n");
return CR_FAILURE;
}
CoreSuspender suspend;

auto unit = Gui::getSelectedUnit(out, true);
if (!unit) {
Expand Down
Loading
Loading