Creating and Using a Queue in Dragon Native

The Dragon Native Queue implementation is Dragon’s specialized implementation of a Queue, working in both single and multi node settings. It is interoperable in all supported languages. The API is similar to Python’s Multiprocessing.Queue in many ways, but has a number of extensions and simplifications. In particular, the queue can be initialized as joinable, which allows to join on the completion of an item.

Using a Queue with Python

The Dragon Native Queue in Python mirrors the multiprocessing.Queue interface but works across multiple nodes and supports joinable queues. Import it from dragon.native.queue:

Listing 39 Creating and using a Dragon Native Queue in Python
 1import dragon
 2from multiprocessing import set_start_method
 3from dragon.native.process import Process
 4from dragon.native.queue import Queue
 5
 6def producer(q):
 7    for i in range(5):
 8        q.put(f"item-{i}")
 9    q.put(None)  # sentinel to signal completion
10
11def consumer(q):
12    while True:
13        item = q.get()
14        if item is None:
15            break
16        print(f"Received: {item}", flush=True)
17
18if __name__ == "__main__":
19    set_start_method("dragon")
20
21    q = Queue()
22    p_prod = Process(target=producer, args=(q,))
23    p_cons = Process(target=consumer, args=(q,))
24
25    p_cons.start()
26    p_prod.start()
27    p_prod.join()
28    p_cons.join()

The queue is serializable: it can be passed as an argument to any managed process, on any node in the Dragon runtime, just like a multiprocessing.Queue . For joinable queues, initialize with joinable=True and call task_done() after processing each item, then use join() to wait for all items to be processed:

Listing 40 Using a joinable Dragon Native Queue
 1import dragon
 2from multiprocessing import set_start_method
 3from dragon.native.process import Process
 4from dragon.native.queue import Queue
 5
 6def worker(q):
 7    while True:
 8        item = q.get()
 9        if item is None:
10            q.task_done()  # balance the sentinel's task count
11            break
12        print(f"Processing: {item}", flush=True)
13        q.task_done()
14
15if __name__ == "__main__":
16    set_start_method("dragon")
17
18    q = Queue(joinable=True)
19    p = Process(target=worker, args=(q,))
20    p.start()
21
22    for i in range(10):
23        q.put(f"work-{i}")
24
25    q.join()    # block until all items have been processed
26    q.put(None)  # stop the worker
27    p.join()

See dragon.native.queue.Queue for the full API reference.

Using a Queue with C++

Dragon’s native Queue has a C++ interface provided by the dragon::Queue<T> class template declared in <dragon/queue.hpp>. There is no separate C API; C and C++ code both use this C++ template. A Queue is created in Python and then attached from C++ using its serialized descriptor, so the Queue’s lifetime is managed by the Python code that created it.

The T type parameter is a serializable value type. Dragon ships several ready-made serializable wrappers in <dragon/serializable.hpp> (for example dragon::SerializableString, dragon::SerializableByteBuffer, dragon::SerializableScalar, and dragon::SerializableVector). You can also implement your own by subclassing dragon::SerializableBase.

First, create the Queue in Python and pass its serialized descriptor to the C++ program. Use the Queue’s serialize() method, which returns a base64-encoded descriptor intended for the C++ Queue to attach to:

Listing 41 run_cpp_queue.py — create the Queue and launch the C++ program
 1import dragon
 2from multiprocessing import set_start_method
 3from dragon.native.queue import Queue
 4import subprocess
 5
 6if __name__ == "__main__":
 7    set_start_method("dragon")
 8
 9    q = Queue()
10    serialized = q.serialize()  # base64 FLI descriptor for the C++ side
11
12    result = subprocess.run(
13        ["./cpp_queue_demo", serialized],
14        capture_output=True, text=True,
15    )
16    print("C++ stdout:", result.stdout.strip())

The C++ program attaches to that Queue, then puts and gets a value:

Listing 42 cpp_queue_demo.cpp — attach to a Dragon Queue and put/get a value
 1#include <dragon/queue.hpp>
 2#include <dragon/serializable.hpp>
 3#include <iostream>
 4
 5int main(int argc, char* argv[]) {
 6    if (argc < 2) {
 7        std::cerr << "usage: " << argv[0] << " <serialized_queue_descr>\n";
 8        return 1;
 9    }
10
11    try {
12        // Attach to the Python-created queue (NULL => use the default pool).
13        dragon::Queue<dragon::SerializableString> q(argv[1], nullptr);
14
15        // Put a value, then get it back.
16        dragon::SerializableString msg(std::string("Hello from C++"));
17        q.put(msg);
18
19        dragon::SerializableString got = q.get();
20        std::cout << "C++ received: " << got.getVal() << std::endl;
21    } catch (const dragon::DragonError& e) {
22        std::cerr << "DragonError: " << e.err_str() << std::endl;
23        return 1;
24    }
25    return 0;
26}

Compile against the Dragon headers and link against libdragon. The dragon-config command emits the correct include and link flags for your installation:

g++ -std=c++17 cpp_queue_demo.cpp \
    $(dragon-config -o) $(dragon-config -l) -o cpp_queue_demo

Then run the Python driver under the Dragon runtime. The C++ subprocess inherits the runtime environment, so it can attach to the default memory pool:

dragon run_cpp_queue.py

The program prints:

C++ stdout: C++ received: Hello from C++

See <dragon/queue.hpp> for the full C++ Queue API, including put, get, get_nowait, poll, and their timeout-aware overloads.