Skip to content

Commit 6356145

Browse files
committed
Fix SHM truncation handling
1 parent 472e211 commit 6356145

2 files changed

Lines changed: 126 additions & 8 deletions

File tree

concore.hpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class Concore{
4040
string inpath = "./in";
4141
string outpath = "./out";
4242

43+
// Shared memory segment size in bytes.
44+
// Increase this constant if your payloads exceed 4096 bytes.
45+
// All nodes in a study must be compiled with the same SHM_SIZE.
46+
// Payloads >= SHM_SIZE throw std::runtime_error (no silent truncation).
4347
static constexpr size_t SHM_SIZE = 4096;
4448

4549
int shmId_create = -1;
@@ -683,9 +687,13 @@ class Concore{
683687
outfile<<val[val.size()-1]<<']';
684688
std::string result = outfile.str();
685689
if (result.size() >= SHM_SIZE) {
686-
std::cerr << "ERROR: write_SM payload (" << result.size()
687-
<< " bytes) exceeds " << SHM_SIZE - 1
688-
<< "-byte shared memory limit. Data truncated!" << std::endl;
690+
throw std::runtime_error(
691+
"concore SHM write failed: payload (" +
692+
std::to_string(result.size()) +
693+
" bytes) exceeds SHM_SIZE (" +
694+
std::to_string(SHM_SIZE) +
695+
"). Aborting. No data written. Increase SHM_SIZE in concore.hpp."
696+
);
689697
}
690698
std::strncpy(sharedData_create, result.c_str(), SHM_SIZE - 1);
691699
sharedData_create[SHM_SIZE - 1] = '\0';
@@ -711,15 +719,19 @@ class Concore{
711719
void write_SM(int port, string name, string val, int delta=0){
712720
chrono::milliseconds timespan((int)(2000*delay));
713721
this_thread::sleep_for(timespan);
722+
if (val.size() >= SHM_SIZE) {
723+
throw std::runtime_error(
724+
"concore SHM write failed: payload (" +
725+
std::to_string(val.size()) +
726+
" bytes) exceeds SHM_SIZE (" +
727+
std::to_string(SHM_SIZE) +
728+
"). Aborting. No data written. Increase SHM_SIZE in concore.hpp."
729+
);
730+
}
714731
try {
715732
if(shmId_create != -1){
716733
if (sharedData_create == nullptr)
717734
throw 506;
718-
if (val.size() >= SHM_SIZE) {
719-
std::cerr << "ERROR: write_SM payload (" << val.size()
720-
<< " bytes) exceeds " << SHM_SIZE - 1
721-
<< "-byte shared memory limit. Data truncated!" << std::endl;
722-
}
723735
std::strncpy(sharedData_create, val.c_str(), SHM_SIZE - 1);
724736
sharedData_create[SHM_SIZE - 1] = '\0';
725737
}

tests/test_shm_abort.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import shutil
2+
import subprocess
3+
import sys
4+
import tempfile
5+
import textwrap
6+
from pathlib import Path
7+
8+
import pytest
9+
10+
11+
REPO_ROOT = Path(__file__).resolve().parent.parent
12+
13+
pytestmark = pytest.mark.skipif(
14+
shutil.which("g++") is None,
15+
reason="g++ not available",
16+
)
17+
18+
19+
@pytest.fixture(autouse=True)
20+
def _skip_windows():
21+
if sys.platform == "win32":
22+
pytest.skip("SHM requires POSIX")
23+
24+
25+
def _compile_and_run(payload_size):
26+
with tempfile.TemporaryDirectory(prefix="concore_shm_test_") as temp_dir:
27+
temp_path = Path(temp_dir)
28+
(temp_path / "concore.oport").write_text('{"1": "1"}', encoding="utf-8")
29+
30+
source_file = temp_path / "shm_abort_test.cpp"
31+
binary_file = temp_path / "shm_abort_test"
32+
source_file.write_text(
33+
textwrap.dedent(
34+
f'''
35+
#include "concore.hpp"
36+
#include <exception>
37+
#include <string>
38+
39+
int main() {{
40+
try {{
41+
Concore concore;
42+
concore.delay = 0;
43+
concore.simtime = 0;
44+
std::string payload({payload_size}, 'a');
45+
concore.write(1, "payload", payload);
46+
return 0;
47+
}} catch (const std::exception& error) {{
48+
std::cerr << error.what() << std::endl;
49+
return 1;
50+
}}
51+
}}
52+
'''
53+
).lstrip(),
54+
encoding="utf-8",
55+
)
56+
57+
compile_result = subprocess.run(
58+
[
59+
"g++",
60+
"-std=c++17",
61+
"-I",
62+
str(REPO_ROOT),
63+
"-o",
64+
str(binary_file),
65+
str(source_file),
66+
],
67+
capture_output=True,
68+
text=True,
69+
timeout=60,
70+
cwd=temp_path,
71+
)
72+
if compile_result.returncode != 0:
73+
pytest.fail(f"g++ compile failed:\n{compile_result.stderr}")
74+
75+
return subprocess.run(
76+
[str(binary_file)],
77+
capture_output=True,
78+
text=True,
79+
timeout=10,
80+
cwd=temp_path,
81+
)
82+
83+
84+
def test_oversized_payload_throws():
85+
result = _compile_and_run(5000)
86+
assert result.returncode != 0
87+
assert "Aborting" in result.stderr
88+
assert "truncated" not in result.stderr.lower()
89+
90+
91+
def test_within_limit_succeeds():
92+
result = _compile_and_run(100)
93+
assert result.returncode == 0
94+
assert result.stderr == ""
95+
96+
97+
def test_exactly_at_limit_throws():
98+
result = _compile_and_run(4096)
99+
assert result.returncode != 0
100+
assert "Aborting" in result.stderr
101+
102+
103+
def test_one_under_limit_succeeds():
104+
result = _compile_and_run(4095)
105+
assert result.returncode == 0
106+
assert result.stderr == ""

0 commit comments

Comments
 (0)