CPU consumption goes up indefintely as time goes when exposing a chaiscript function with state to C++


#1

Hello everyone,

I am having a strange problem. I do not know if the problem is a bug in ChaiScript or wether I am doing something wrong.

Here it goes.

I have a task scheduler written in C++. The task scheduler goes something like this:

class Task {
public:
    //...
   bool onUpdate(int elapsed);
private:
   virtual bool doOnUpdate(int elapsed) = 0;
};

class FunctionTask : Task {
    public:
        std::function<bool (int)> updateFunc_;

        virtual bool doOnUpdate(int elapsed) override { return updateFunc_(elapsed); }
};

class TaskScheduler {
public:
   //Noncopyable, nonmovable
    shared_ptr<Task> addTask(std::function<bool (int)> task);
    void run();
};

Now I have a function spawn, that is exposed to ChaiScript:

shared_ptr<Task> spawn(std::function<bool (int)> task) {
       shared_ptr<Task> taskPtr = createTask<FunctionTask>(std::move(task));
       getDefaultScheduler().addTask(Ptr);
       return task;
}

I exposed spawn:

chai.add(fun(&spawn), "spawn");

I also expose the TaskScheduler addTask and run().

Now, from my script I do the following:

var makeChoiceTask = fun[inputSystem, gameView](unused) {//Wait for a menu choice
    if (menuChoice != 0) {
        inputSystem.dispatchEvents()
        thisThreadSleepFor(5)
        gameView.draw()
        return false
    }
    else {
        getDefaultTaskScheduler().requestExitAll()
        return true
    }
    return false
}

spawn(makeMenuChoice)
taskScheduler.run()

I have instrumented the binary and I see that, as time goes, the following function keeps being called every time more intensively:

chaiscript::Boxed_Value::operator=(chaiscript::Boxed_Value&&)

My scheduler calls task->onUpdate(elapsed) which will eventually call the ChaiScript function. I cannot even figure out why this is happening, but I am completely sure and I verified in several ways that it is putting the chaiscript function in the TaskScheduler is what is triggering an increasingly intensive call to that function.


#2

I have more information on this problem.

The problem is easy to reproduce with my TaskScheduler but I think it will be reproducible in many other ways. How to reproduce:

  1. use a function<> in C++ side.

  2. create a function in ChaiScript and pass that function to the C++ side. The function must contain state that is ChaiScript side and that state must be used inside the function. For example.

    var a = 10
    var myFunc = funa { a.to_string(); }

  3. call repeteadly the function in C++ side.

    //C++ side
    function<void ()> f;
    //assign to f myFunc from ChaiScript
    while (1) { f(); }
    

With these steps I am able to observe an ever increasing cpu usage until it reaches 100%.


#3

I would expect that if you have a tight loop making function calls that this would consume 100% of the CPU, so I’m not sure the second example actually demonstrates what you want it to. Particularly since it should not be calling the operator=(Boxed_Value &&) function you are reference in the first example.

Do you by any chance have any more information since you last posted this?


#4

My loop stops for n milliseconds in each iteration. I do the same in C++. In ChaiScript it seems to grow steadily as it loops, without doing anything. In C++ it does not happen. I tested it myself in Mac OS X. I really think it is representative of what should not happen, since that loop is stopping for n millisecs per iteration, unless I am not seeing the problem myself and making wrong assumptions.

You can see in makeMenuChoice this:

thisThreadSleepFor(5)

That should make the CPU not be 100% since most of the time it is idle.


#5

Tried to reproduce like this:

// g++ -std=c++17 -I ../submodules/ChaiScript/include/ chaiscipt_call.cpp -o /tmp/chaiscript_call -pthread -ldl

#include <chaiscript/chaiscript.hpp>

#include <string>
#include <thread>
#include <chrono>  
#include <iostream>

int main() {
        chaiscript::ChaiScript chai;

        chai.eval(R"(
                var i = 0;    
                var test = fun[i]() {
                        i += 1;
                        return i.to_string(); 
                }
        )");

        using namespace std::chrono_literals;

        auto test = chai.eval< std::function<std::string()> >("test");
        while(true) {  
                std::cout << test() << '\r';
                std::this_thread::sleep_for(1ms);
        }
}

CPU utilization stays constant around 5% on old 4ghz Xeon. Used release 6.0.0 for testing.

EDIT: on debian linux


#6

This is a full example that for sure triggered the problem: