aboutsummaryrefslogtreecommitdiff
path: root/src/lane.h
blob: 0426240edbb971f2df0cce21a346e2f4f9b652be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#pragma once

#include "cancel.h"
#include "uniquekey.h"
#include "universe.h"

// #################################################################################################

// xxh64 of string "kExtendedStackTraceRegKey" generated at https://www.pelock.com/products/hash-calculator
static constexpr RegistryUniqueKey kExtendedStackTraceRegKey{ 0x38147AD48FB426E2ull }; // used as registry key

/*
 * registry[FINALIZER_REG_KEY] is either nil (no finalizers) or a table
 * of functions that Lanes will call after the executing 'pcall' has ended.
 *
 * We're NOT using the GC system for finalizer mainly because providing the
 * error (and maybe stack trace) arguments to the finalizer functions would
 * anyways complicate that approach.
 */
// xxh64 of string "kFinalizerRegKey" generated at https://www.pelock.com/products/hash-calculator
static constexpr RegistryUniqueKey kFinalizerRegKey{ 0xFE936BFAA718FEEAull };

// xxh64 of string "kLaneGC" generated at https://www.pelock.com/products/hash-calculator
static constexpr UniqueKey kLaneGC{ 0x5D6122141727F960ull };

// xxh64 of string "kLanePointerRegKey" generated at https://www.pelock.com/products/hash-calculator
static constexpr RegistryUniqueKey kLanePointerRegKey{ 0x2D8CF03FE9F0A51Aull }; // used as registry key

    // xxh64 of string "debugName" generated at https://www.pelock.com/products/hash-calculator
static constexpr RegistryUniqueKey kLaneNameRegKey{ 0xA194E2645C57F6DDull };

// #################################################################################################

// The chain is ended by '(Lane*)(-1)', not nullptr: 'selfdestructFirst -> ... -> ... -> (-1)'
#define SELFDESTRUCT_END ((Lane*) (-1))

// must be a #define instead of a constexpr to benefit from compile-time string concatenation
#define kLaneMetatableName "Lane"
#define kLanesLibName "lanes"
#define kLanesCoreLibName kLanesLibName ".core"

// NOTE: values to be changed by either thread, during execution, without locking, are marked "volatile"
class Lane
{
    public:
    /*
      Pending: The Lua VM hasn't done anything yet.
      Running, Waiting: Thread is inside the Lua VM. If the thread is forcefully stopped, we can't lua_close() the Lua State.
      Done, Error, Cancelled: Thread execution is outside the Lua VM. It can be lua_close()d.
    */
    enum class Status
    {
        Pending,
        Running,
        Waiting,
        Done,
        Error,
        Cancelled
    };
    using enum Status;

    enum class ErrorTraceLevel
    {
        Minimal, // no error handler function when running the lane body
        Basic, // lane body errors caught by a error handler
        Extended // same as above, but with more data extracted from the debug infos
    };
    using enum ErrorTraceLevel;

    // the thread
    std::thread thread; // use jthread if we ever need a stop_source
#ifndef __PROSPERO__
    // a latch to wait for the lua_State to be ready
    std::latch ready{ 1 };
#else // __PROSPERO__
    std::atomic_flag ready{};
#endif // __PROSPERO__
    // to wait for stop requests through thread's stop_source
    std::mutex doneMutex;
    std::condition_variable doneCondVar; // use condition_variable_any if waiting for a stop_token
    //
    // M: sub-thread OS thread
    // S: not used

    std::string_view debugName{ "<unnamed>" };

    Universe* const U;
    lua_State* L;
    //
    // M: prepares the state, and reads results
    // S: while S is running, M must keep out of modifying the state

    Status volatile status{ Pending };
    //
    // M: sets to Pending (before launching)
    // S: updates -> Running/Waiting -> Done/Error/Cancelled

    std::condition_variable* volatile waiting_on{ nullptr };
    //
    // When status is Waiting, points on the linda's signal the thread waits on, else nullptr

    CancelRequest volatile cancelRequest{ CancelRequest::None };
    //
    // M: sets to false, flags true for cancel request
    // S: reads to see if cancel is requested

    Lane* volatile selfdestruct_next{ nullptr };
    //
    // M: sets to non-nullptr if facing lane handle '__gc' cycle but the lane
    //    is still running
    // S: cleans up after itself if non-nullptr at lane exit

    // For tracking only
    Lane* volatile tracking_next{ nullptr };

    ErrorTraceLevel const errorTraceLevel{ Basic };

    [[nodiscard]] static void* operator new(size_t size_, Universe* U_) noexcept { return U_->internalAllocator.alloc(size_); }
    // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception
    static void operator delete(void* p_, Universe* U_) { U_->internalAllocator.free(p_, sizeof(Lane)); }
    // this one is for us, to make sure memory is freed by the correct allocator
    static void operator delete(void* p_) { static_cast<Lane*>(p_)->U->internalAllocator.free(p_, sizeof(Lane)); }

    Lane(Universe* U_, lua_State* L_, ErrorTraceLevel errorTraceLevel_);
    ~Lane();

    private:

    [[nodiscard]] CancelResult cancelHard(std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_);
    [[nodiscard]] CancelResult cancelSoft(std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_);

    public:

    CancelResult cancel(CancelOp op_, int hookCount_, std::chrono::time_point<std::chrono::steady_clock> until_, bool wakeLane_);
    void changeDebugName(int const nameIdx_);
    void closeState() { lua_State* _L{ L }; L = nullptr; lua_close(_L); }
    [[nodiscard]] std::string_view errorTraceLevelString() const;
    [[nodiscard]] int pushErrorHandler() const;
    [[nodiscard]] std::string_view pushErrorTraceLevel(lua_State* L_) const;
    static void PushMetatable(lua_State* L_);
    void pushStatusString(lua_State* L_) const;
    void securizeDebugName(lua_State* L_);
    void startThread(int priority_);
    [[nodiscard]] std::string_view threadStatusString() const;
    [[nodiscard]] bool waitForCompletion(std::chrono::time_point<std::chrono::steady_clock> until_);
};

// #################################################################################################

// To allow free-running threads (longer lifespan than the handle's)
// 'Lane' are malloc/free'd and the handle only carries a pointer.
// This is not deep userdata since the handle is not portable among lanes.
//
[[nodiscard]] inline Lane* ToLane(lua_State* L_, int i_)
{
    return *(static_cast<Lane**>(luaL_checkudata(L_, i_, kLaneMetatableName)));
}