Repository navigation
Conversation
libmbus records every failure's reason in one file-scope error_str[512] and returns the normalized XML render's strings from function-static buffers, so all threads in a process share them. A program that drives M-Bus contexts from several threads therefore reads another thread's failure reason (the TCP transport writes error_str on the receive timeout at the end of each exchange), and two concurrent mbus_frame_data_xml_normalized() calls tear each other's output. Declare error_str and the eight function-static buffers reachable from mbus_parse(), mbus_frame_data_parse() and mbus_frame_data_xml_normalized() __thread. No signature, allocation or single-threaded behaviour changes; each returned pointer is the calling thread's array, and every caller inside the library copies it immediately.
Collaborator
|
Thank you for your contribution. This library wasn't designed for a multi-thread environment. So in case this would be merged, users would expect the library to handle multi-thread in all situations. I'm sorry, but I don't have the resources to maintain such a feature. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
libmbus records every failure's reason in one file-scope
static char error_str[512](returned bymbus_error_str()), and the normalized XML render returns its strings from function-static buffers. All threads in a process share both, so a program that drives M-Bus contexts from several threads:error_stron the receive timeout at the end of each exchange (mbus_tcp_recv_frame), so the reason a thread reads after its own failed exchange or decode can belong to another context's exchange, or be a mix of two.mbus_frame_data_xml_normalized()calls overwrite each other's header and record strings, so the XML carries another meter's fields or stops being well-formed.A caller can serialize its own decodes, but no lock in the caller covers the transport's writes to
error_strwithout serializing every meter exchange in the process.Change
This declares
__threadonerror_strand on the eight function-static buffers thatmbus_parse(),mbus_frame_data_parse()andmbus_frame_data_xml_normalized()reach:error_strbuffmbus_data_variable_header_xmlm_strmbus_decode_manufacturerbuffmbus_data_product_namebuffmbus_data_variable_medium_lookupbuffmbus_data_record_functionbuffmbus_data_fixed_mediumbuffmbus_data_fixed_unitbuffmbus_data_fixed_functionNothing else changes: no signature, no allocation, no single-threaded behaviour. Each returned pointer is now the calling thread's array, valid for that thread's lifetime, and every caller inside the library copies it immediately (
snprintf,strdup,mbus_str_xml_encode).Out of scope here:
mbus_data_variable_xml/mbus_frame_xmlrecord helpers and their VIF/unit lookups,mbus_data_error_lookup,mbus_frame_get_secondary_address, which already has_to_buffersince Update mbus_frame_get_secondary_address() API #227);tcp_timeout_sec/_usecand the debug flags, which are process-wide configuration.I can extend the change to the eleven buffers if you prefer them all handled the same way.
Cost
The patched
libmbus.sogets a 5,764-byte TLS segment (x86-64, default flags; the stock library has none). glibc lays out the TLS of a library loaded at program start in every thread's static TLS area and zeroes it when the thread is created, so every thread of a program linking libmbus carries it, including threads that never call the library. Measured with 256 idle threads: 13.0 KiB RSS per thread against 9.1 KiB stock, i.e. one extra page.Portability
__threadrather than C11_Thread_local, becauseconfigure.acsets no-std: gcc and clang accept__threadwithout a diagnostic in every C mode, while_Thread_localdraws a pedantic warning below C11. Built with gcc (x86-64, riscv64, i686) and clang. MSVC spells it__declspec(thread); if a Windows build matters, I can add a small macro.Testing
test/generate-xml.shovertest-frames,error-framesandunsupported-framesgives the same exit codes on stock and patched builds.mbus_parse_hexandmbus_parse_hex -noutput for all 78 test frames is byte-identical between the two builds.mbus_error_str()differingac52f3fReproducer:
mbus_thread_repro.c