Commit f226ff1
committed
Close all nineteen discarded write and load results, and four more defects with them
The prerequisite row has called ignored return values the most serious class of defect
in this tree for a while, and then listed two NUL truncations as what was left. That
list was wrong. Counted rather than estimated, there were nineteen call sites that
discarded the result of a write or a load, and this closes every one.
WHY THEY GOT WORSE THIS MORNING BEFORE THEY GOT BETTER
Twelve of the nineteen are `MessageData::Write`. Until today a failed Write produced a
message with no body and returned true, so those twelve wrote a broken message and
carried on. This morning's MIME work made Write refuse rather than commit a body-less
message - which is correct, and which turned all twelve into silent no-ops: the header
a rule set, the trace header on a local delivery, the vacation body, the virus
notification simply do not get written and delivery continues as though they had.
That is the shape worth naming. Fixing a layer properly can make the defects above it
LESS visible rather than more, and the twelve were about to disappear into "works
fine". They now go through `MessageData::WriteReported`, which reports what has not
been written and names it.
Four of the twelve are not modifications of an existing message but the FIRST write of
one built from nothing - a rule-generated reply, a delivery-failure notification, an
out-of-office reply, a virus notification. For those, discarding the result queued a
message row whose file does not exist and left the delivery to trip over it. They now
abort: no reply at all is better than a reply the recipient can never read, and each of
those functions already returned early on other conditions.
THE OTHER SEVEN, EACH HANDLED THE WAY ITS OWN SURROUNDINGS ALREADY HANDLE FAILURE
`VirusScanner` had two, and they were the ones with teeth. The scanner makes two passes
- the whole file, then each attachment separately, because a scanner that does not
decode MIME will not see inside a base64 part. Both inputs to the second pass were
taken on trust: a message that could not be parsed produced an EMPTY attachment list,
so the loop ran over nothing and the function returned "no virus found"; and a failed
temp-file write left `ScanFile_` scanning a file that was missing or empty, which comes
back clean. Neither is unscanned mail - the whole file is scanned first - which is why
these report (6001, 6002) rather than refusing the message. Refusing on a transient
file lock would turn a degraded scan into rejected mail.
`SpamAssassinClient` counted bytes it had not written, so a short write could satisfy
the length check and hand back a truncated message to replace the original. It now
aborts the response, which is exactly what the two other failure paths in that function
already do.
`MessageAttachmentStripper` rewrites a message from what it read, and read it without
checking. Its outcome was accidentally safe rather than deliberately so - it depended on
FindFirstPart returning nothing for an empty parse - so it now says what happened and
leaves the file alone.
`Attachment::SaveAs` is reached from COM, where a script has just been told the save
worked. It is void, so a full disk looked exactly like success and the script moved on
to the next attachment. Reported as 6003.
`IMAPFetch` answered the client from an empty body when the load failed: a message that
renders blank, with an OK, which the client then caches and stops asking for. Now
reported. Be clear about what that does not fix - `ReportCriticalError_` says in its own
comment that it reports "and then throws an exception", and it does not throw, so the
blank message still goes out. That is written up rather than changed, because making it
throw alters the failure semantics of every FETCH and wants its own test.
`Logger`'s two are the one failure in the server that cannot be logged: LOG_APPLICATION
comes straight back, and ErrorManager writes the error log through the same function, so
on the failing disk that provokes it the report would re-enter what just failed, once
per line. They go once per process to the debugger and the Windows event log instead. A
mail server whose audit trail has silently stopped used to look exactly like a quiet
one.
FOUR MORE, FOUND WHILE DOING IT
`Event::WaitFor` returned void - with a comment beside the discarded result reading
"result will be false if there's a timeout". A caller could bound a wait and not be told
which of the two happened, which is the entire point of bounding it. The predicate form
also fixes a quieter bug: a bare `wait_for` returns on a spurious wakeup too, and the
old code then cleared the flag and carried on as though the event had fired.
That was the missing piece for `ExternalFetch::Start`, which ended in an UNBOUNDED wait
on a work-queue thread, set only by `~TCPConnection`, with the POP3 client setting an
idle timeout and no absolute ceiling. A remote server sending one byte before each
expiry held that fetch thread indefinitely; enough accounts in that state and external
fetching stops for every account on the server. The fix is not just a timeout: `Start`
drops its reference to the connection before waiting, so timing out would return while
the session is still live and let the next cycle collect the same mailbox concurrently -
the duplicate-delivery shape half of today was spent removing. It keeps a weak reference
and disconnects what it abandons, through `EnqueueDisconnect` so the shutdown is posted
to the strand that owns the socket rather than called across threads. One hour, chosen as
a backstop for the pathological case and not as a performance bound.
`SqlLogDevice` sent `PRIMARY KEY CLUSTERED` to SQL CE, which does not accept it. Every
constraint in the script SQL CE actually runs is NONCLUSTERED, and the one CLUSTERED
form in it is stripped by `SQLScriptParser::PreprocessLine_` before SQL CE sees it -
these statements do not go through that parser. It failed invisibly because the line
carries `[IGNORE-ERRORS]`, so on the installer's DEFAULT backend the SQL log device has
been running without a primary key on hm_log for as long as this has been there.
`ScheduledTask::SetNextRunTime` computed "now + interval" after the task returned, so
the real period was interval plus however long the task took and every pass drifted by
its own duration. Nothing was ever skipped, so nothing ever looked wrong - the runs just
wandered away from the times the administrator set. Now measured from the run that was
due, with a guard so a task that overran its own interval resumes the cadence instead of
running back-to-back to catch up.
VERIFICATION
Release build clean. Full suite green.1 parent 0d1fd5f commit f226ff1
22 files changed
Lines changed: 307 additions & 46 deletions
File tree
- hmailserver/source/Server
- Common
- AntiSpam
- SpamAssassin
- AntiVirus
- Application
- BO
- Util
- ExternalFetcher
- IMAP
- SMTP
Large diffs are not rendered by default.
Lines changed: 15 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
179 | 179 | | |
180 | 180 | | |
181 | 181 | | |
182 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
183 | 190 | | |
184 | | - | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
185 | 198 | | |
186 | 199 | | |
187 | 200 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
| 144 | + | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
| |||
Lines changed: 35 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
218 | | - | |
| 218 | + | |
219 | 219 | | |
220 | | - | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
221 | 238 | | |
222 | 239 | | |
223 | 240 | | |
| |||
230 | 247 | | |
231 | 248 | | |
232 | 249 | | |
233 | | - | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
234 | 264 | | |
235 | 265 | | |
236 | 266 | | |
| |||
305 | 335 | | |
306 | 336 | | |
307 | 337 | | |
308 | | - | |
309 | | - | |
| 338 | + | |
| 339 | + | |
310 | 340 | | |
311 | 341 | | |
312 | 342 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
595 | 595 | | |
596 | 596 | | |
597 | 597 | | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
598 | 602 | | |
599 | 603 | | |
600 | | - | |
| 604 | + | |
| 605 | + | |
601 | 606 | | |
602 | 607 | | |
603 | 608 | | |
| |||
616 | 621 | | |
617 | 622 | | |
618 | 623 | | |
619 | | - | |
| 624 | + | |
| 625 | + | |
620 | 626 | | |
621 | 627 | | |
622 | 628 | | |
623 | 629 | | |
624 | 630 | | |
625 | 631 | | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
626 | 670 | | |
627 | 671 | | |
628 | 672 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
202 | 206 | | |
203 | 207 | | |
204 | 208 | | |
| |||
Lines changed: 10 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
801 | 801 | | |
802 | 802 | | |
803 | 803 | | |
804 | | - | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
805 | 814 | | |
806 | 815 | | |
807 | 816 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
34 | 43 | | |
35 | 44 | | |
36 | 45 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
832 | 832 | | |
833 | 833 | | |
834 | 834 | | |
835 | | - | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
836 | 858 | | |
837 | 859 | | |
838 | 860 | | |
| |||
964 | 986 | | |
965 | 987 | | |
966 | 988 | | |
967 | | - | |
| 989 | + | |
968 | 990 | | |
969 | 991 | | |
970 | 992 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
74 | 86 | | |
75 | 87 | | |
76 | 88 | | |
| |||
0 commit comments