Skip to content

dpdk: use DPDK EAL threads for workers v5.1#14691

Open
lukashino wants to merge 5 commits intoOISF:mainfrom
lukashino:feat/8084-initialize-eal-threads-v5.1
Open

dpdk: use DPDK EAL threads for workers v5.1#14691
lukashino wants to merge 5 commits intoOISF:mainfrom
lukashino:feat/8084-initialize-eal-threads-v5.1

Conversation

@lukashino
Copy link
Contributor

Continuation of #14619
Link to ticket: https://redmine.openinfosecfoundation.org/issues/8084

Describe changes:
DPDK EAL threads are a construct in DPDK that envelopes the classic pthreads. DPDK EAL-native threads, though, offer some advantages - one of which is the use of mempool caches or the ability to run as a secondary process.
The initialization is automatic and leverages the already defined threading affinity from which it composes a list of lcores (DPDK EAL parameter "-l"). Based on this list of CPU cores, the DPDK EAL init function then initializes the threads. Suricata, during the startup, later launches worker functions on these threads.

v5:

  • abstract specific threading functions from threading code (capture method-specific for the receive module)

v4 (public initial work):

  • remove redundant pthread_exit function calls - they are just before function return statements anyway so they serve no additional purpose
  • initilialize the lcore list from the threading - this is a little tough problem because an interface/cpu-set might have more cores defined in the affinity section than are realistically used (thread count in interface node). It additionally might not use the threads sequentially as with autopin it pins to NUMA-relevant CPUs. Currently, it createsa superset of lcores (it merges all relevant affinities together) and "in the worst case" some threads remain idle.
  • launch actual worker function on the lcores. This needs to combine the previously disjunct (although tightly connected) parts - thread spawning and affinity setting. DPDK has no thread spawning, the threads already exist after EAL init and are just instructed to launch a certain function on a certain, specific lcore. I wanted to avoid mixing DPDK launching logic with pthread affinity logic to prevent confusing/bypassing DPDK's EAL layer.

Automated testing is constrained at the moment.

Lukas Sismis added 5 commits January 25, 2026 22:28
As the next step in the current implementation after pthread_exit
was to end the thread execution anyway.
Lcore initialization is needed to leverage additional DPDK features,
e.g., packet mempool per-core cache.

Threads are loaded from the CPU affinity of active interfaces,
meaning, interfaces which are defined under dpdk.interfaces list.

The list is superset of actually used cores. This means that DPDK
EAL init may create more lcores than actually needed. It may happen
when defining affinity for e.g. "all" but the device/interface only
using 4 threads.
Additional consideration should be on autopin which can pick cores
from the cpu affinity list non-sequentially but rather according to
NUMA mapping.

Lcore super set is not known to introduce any functional or
performance  problem.

Ticket: 8084
As a prerequisite for the upcoming DPDK thread management,
this commit allows custom spawn and join (wait) thread functions
to be configured.

Ticket: 8084
This change leverages DPDK functions from rte_eal*
for launching worker function on the selected
DPDK lcores.

Ticket: 8084
Copilot AI review requested due to automatic review settings January 26, 2026 10:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 71.15385% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.11%. Comparing base (b944e3b) to head (f675be2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #14691      +/-   ##
==========================================
- Coverage   82.13%   82.11%   -0.03%     
==========================================
  Files        1011     1012       +1     
  Lines      262922   263101     +179     
==========================================
+ Hits       215956   216033      +77     
- Misses      46966    47068     +102     
Flag Coverage Δ
fuzzcorpus 60.22% <4.25%> (-0.01%) ⬇️
livemode 18.77% <71.15%> (+0.03%) ⬆️
pcap 44.58% <34.04%> (-0.08%) ⬇️
suricata-verify 65.30% <34.04%> (-0.03%) ⬇️
unittests 59.26% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@suricata-qa
Copy link

Information: QA ran without warnings.

Pipeline = 29272

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

tv->thread_id = AffinityGetNextCPU(tv, taf);

SCLogPerf("Setting prio %d for thread \"%s\" to cpu/core "
"%" PRIu64 ", thread id %lu",
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The log message uses SCGetThreadIdLong() which returns the calling thread's ID (the main thread spawning the worker), not the DPDK worker thread's ID. Since this log is printed before the DPDK thread is actually launched, it will show the ID of the spawning thread rather than the worker thread's ID. Consider either removing the thread id %lu part or clarifying in the message that this is the spawning thread's ID, not the worker thread's ID.

Suggested change
"%" PRIu64 ", thread id %lu",
"%" PRIu64 ", spawning thread id (current thread) %lu",

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +96
if (CPU_ISSET(tv->thread_id, &taf->lowprio_cpu)) {
tv->thread_priority = PRIO_LOW;
} else if (CPU_ISSET(tv->thread_id, &taf->medprio_cpu)) {
tv->thread_priority = PRIO_MEDIUM;
} else if (CPU_ISSET(tv->thread_id, &taf->hiprio_cpu)) {
tv->thread_priority = PRIO_HIGH;
} else {
tv->thread_priority = taf->prio;
}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The priority is being determined using tv->thread_id on lines 88-93, but tv->thread_id is not set until line 100. This means the CPU_ISSET checks are operating on an uninitialized or stale value of tv->thread_id. The priority determination logic should either use a different variable or be moved after tv->thread_id is assigned on line 100.

Copilot uses AI. Check for mistakes.
Copy link
Member

@victorjulien victorjulien left a comment

Choose a reason for hiding this comment

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

Just looked at the abstraction for now, but that will need some work. Plus some questions inline.

return TM_ECODE_OK;
}

static void TmThreadSpawnPthread(ThreadVars *tv)
Copy link
Member

Choose a reason for hiding this comment

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

I would expect this to live in a new file tm-threads-pthreads.c or similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

by default, I assumed pthread can stay at the core of the code. could be extracted np.


/** \brief Per thread variable structure */
typedef struct ThreadVars_ {
pthread_t t;
Copy link
Member

Choose a reason for hiding this comment

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

pthread_t is an opaque type that we can't assume to fit in a uint64_t

* this thread. It is passed directly to pthread_create(), hence the
* void pointers in and out. */
void *(*tm_func)(void *);
void (*tm_spawn)(struct ThreadVars_ *);
Copy link
Member

Choose a reason for hiding this comment

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

why are these in ThreadVars and not a global thread API table of sorts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe the answer is in

why do we need these in a TmModule instead of globally?

thread

typedef struct TmModule_ {
const char *name;

/** thread management, if unspecified, falls back to pthreads */
Copy link
Member

Choose a reason for hiding this comment

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

why do we need these in a TmModule instead of globally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the DPDK capture method, we only need to create DPDK lcores for worker threads.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is mainly because a single DPDK lcore is not intended to run multiple separate threads.
And that is a common case for, e.g., flow manager/recycler threads.

Copy link
Member

Choose a reason for hiding this comment

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

so the dpdk threading support is only a partial replacement for the pthreads inside a single process? Some threads use the dpdk logic, other pure pthreads?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

correct. DPDK threads still use pthreads at the core so they are compatible but they should be manipulated through DPDK calls ideally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With regards to global setting, we could have thread management callbacks on the level of thread families so it does not need to be stored within ThreadVars directly and repeatedly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants