-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Nim Version
Nim Compiler Version 2.2.6 [Linux: amd64]
Description
I have a program that saves state information including a start timestamp DateTime in a small tuple, which is serialised with marshal and written to file. The next execution loads+deserialises the data from the last run. When this code uses the overloaded - proc on a timestamp which is a DateTime value, it hits a SIGSEGV.
It has taken me a while to narrow down the trigger for the error, and come up with a repeatable example. The demo code below demonstrates (I think)-
- Various "shapes" of data, that include a
DateTimefield, work ok - Serialising + deserialising a
DateTimewithin a single process invocation works ok - Serialising in one process, then reading+deserialising a
DateTimein a separate invocation fails
Demonstrating the Error (test program)
Compile the code below to test_marshal_external
Note- serialised output is written to multiple files (one per data structure) in /tmp/marshal_*.json
Usage: test_marshal_external -[wrd]
w- Write serialised data to /tmp/marshal_.json
r- Read and deserialise data from files
d- Cleanup (delete files in /tmp)
Then try-
test_marshal_external -wrd- Serialises, deserialises, and operates on the DateTime values in one process.test_marshal_external -w- Write data tomarshal-formatted json filestest_marshal_external -r- Reads and operates on the data. Triggers SIGSEGV
Demo Code
import std / [ cmdline, strutils, times, marshal, strformat, os, files ]
type TestDT = DateTime
type TestSimpleTuple = tuple[timestamp : DateTime]
type TestComplexTuple = tuple[
intValue : int,
floatValue : float,
timestamp : DateTime,
stringValue : string,
intArray : array[0..2, int],
floatSeq : seq[float]
]
type TestComplexTuple2 = tuple[
Interactive : bool,
ScanMode : string,
RunStart : DateTime,
ExecuteTime_s : Duration,
TotalProcessorTime_s : float
]
let args = commandLineParams().join("").toLower
echo fmt "{args=}"
echo fmt "\nTime now= {now()}"
if args.contains("w"):
echo "\n****************Serialising + Writing****************"
block:
echo "----Simple DateTime----"
let sourceDT : TestDT = now()
let fPath = "/tmp/marshal_simpleDT.json"
var f = open(fPath, fmWrite)
f.write($$sourceDT)
f.close()
block:
echo "----Single element tuple----"
let sourceTuple : TestSimpleTuple = (timestamp : now())
let fPath = "/tmp/marshal_simpleTuple.json"
var f = open(fPath, fmWrite)
f.write($$sourceTuple)
f.close()
block:
echo "----Multi element tuple----"
let sourceTuple : TestComplexTuple = (
intValue : 1,
floatValue : 1.0,
timestamp : now(),
stringValue : "this is the string value",
intArray : [10,11,12],
floatSeq : @[1.0,1.1,1.2]
)
let fPath = "/tmp/marshal_complexTuple.json"
var f = open(fPath, fmWrite)
f.write($$sourceTuple)
f.close()
block:
echo "----Another Multi element tuple----"
let sourceTuple : TestComplexTuple2 = (
Interactive : true,
ScanMode : "Not specified",
RunStart : now(),
ExecuteTime_s : initDuration(seconds = 4),
TotalProcessorTime_s : 2.82
)
let fPath = "/tmp/marshal_complexTuple2.json"
var f = open(fPath, fmWrite)
f.write($$sourceTuple)
f.close()
if args.contains("r"):
echo "\n****************Reading + Deserialising****************"
block:
echo "----Simple DateTime----"
let fPath = "/tmp/marshal_simpleDT.json"
var f = open(fPath, fmRead)
var deserialised = f.readAll().to[:TestDT]
f.close()
echo fmt "{deserialised=}"
let earlierBy5s : DateTime = deserialised - initDuration(seconds = 5)
echo fmt "{earlierBy5s=}"
block:
echo "----Single element tuple----"
let fPath = "/tmp/marshal_simpleTuple.json"
var f = open(fPath, fmRead)
var deserialised = f.readAll().to[:TestSimpleTuple]
f.close()
echo fmt "{deserialised=}"
let earlierBy5s : DateTime = deserialised.timestamp - initDuration(seconds = 5)
echo fmt "{earlierBy5s=}"
block:
echo "----Complex tuple----"
let fPath = "/tmp/marshal_complexTuple.json"
var f = open(fPath, fmRead)
var deserialised = f.readAll().to[:TestComplexTuple]
f.close()
echo fmt "{deserialised=}"
let earlierBy5s : DateTime = deserialised.timestamp - initDuration(seconds = 5)
echo fmt "{earlierBy5s=}"
block:
echo "----Another Complex tuple----"
let fPath = "/tmp/marshal_complexTuple2.json"
var f = open(fPath, fmRead)
var deserialised = f.readAll().to[:TestComplexTuple2]
f.close()
echo fmt "{deserialised=}"
let earlierBy5s : DateTime = deserialised.RunStart - initDuration(seconds = 5)
echo fmt "{earlierBy5s=}"
if args.contains("d"):
echo "\n****************Cleanup - Deleting tmp files****************"
write(stdout, "\nCleaning up")
for f in [
"/tmp/marshal_simpleDT.json",
"/tmp/marshal_simpleTuple.json",
"/tmp/marshal_complexTuple.json",
"/tmp/marshal_complexTuple2.json"
]:
f.removeFile()
write(stdout, ".")
write(stdout, "\n\n")
Current Output
test_marshal_external' -r [Exec]
args=-r
Time now= 2026-02-20T11:29:01+00:00
****************Reading + Deserialising****************
----Simple DateTime----
deserialised=2026-02-20T11:28:58+00:00
Traceback (most recent call last)
~/Documents/Development/Nim test/test_marshal_external.nim(82) test_marshal_external
~/.choosenim/toolchains/nim-2.2.6/lib/pure/times.nim(1470) -
~/.choosenim/toolchains/nim-2.2.6/lib/pure/times.nim(1268) inZone
~/.choosenim/toolchains/nim-2.2.6/lib/pure/times.nim(1239) zonedTimeFromTime
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Expected Output
$ ./test_marshal_external' -wrd
args=-wrd
Time now= 2026-02-19T18:39:21+00:00
****************Serialising + Writing****************
----Simple DateTime----
----Single element tuple----
----Multi element tuple----
----Another Multi element tuple----
****************Reading + Deserialising****************
----Simple DateTime----
deserialised=2026-02-19T18:39:21+00:00
earlierBy5s=2026-02-19T18:39:16+00:00
----Single element tuple----
deserialised=(timestamp: 2026-02-19T18:39:21+00:00)
earlierBy5s=2026-02-19T18:39:16+00:00
----Complex tuple----
deserialised=(intValue: 1, floatValue: 1.0, timestamp: 2026-02-19T18:39:21+00:00, stringValue: "this is the string value", intArray: [10, 11, 12], floatSeq: @[1.0, 1.1, 1.2])
earlierBy5s=2026-02-19T18:39:16+00:00
----Another Complex tuple----
deserialised=(Interactive: true, ScanMode: "Not specified", RunStart: 2026-02-19T18:39:21+00:00, ExecuteTime_s: 4 seconds, TotalProcessorTime_s: 2.82)
earlierBy5s=2026-02-19T18:39:16+00:00
****************Cleanup - Deleting tmp files****************
Cleaning up....
Known Workarounds
UPDATE
Contrary to (my) expectations, it seems you cannot "un-poison" a DateTime that has been poisoned in this manner by "round-tripping" through serialisation within the current process. I.e. this does NOT work-
let shiftedTimestamp : DateTime = $$poisonedTimestamp.to[:DateTime] - initDuration(seconds = 5)
However you can un-poison the value by (in effect) printing the DateTime to a string and then building a new DateTime value by parsing the string. It's ugly, but it works-
# Un-poison the deserialised DateTime value before attempting to use it for calculation-
const defaultFormat = "yyyy-MM-dd'T'HH:mm:sszzz"
let shiftedTimestamp : DateTime = ($poisonedTimestamp).parse(defaultFormat) -
initDuration(seconds = 5)
Additional Information
No response