Skip to content

Commit 7a5f52c

Browse files
committed
Address Copilot reviews
1 parent 623cca4 commit 7a5f52c

4 files changed

Lines changed: 113 additions & 81 deletions

File tree

include/tscore/Diags.h

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,35 +60,39 @@
6060
/**
6161
* @brief Holder for the process-global Diags pointer.
6262
*
63-
* Provides write access via set() (single-threaded initialization) and
64-
* read access via the free function diags(). After initialization the
65-
* pointer is read-only and may be read concurrently by any number of threads.
63+
* Provides write access via set() and read access via the free function
64+
* diags(). The pointer is initially null. set() may be called more than
65+
* once to replace the active instance (e.g., to upgrade from a bootstrap
66+
* configuration to a fully records-backed one).
6667
*
67-
* @thread-safety _diags_ptr is written during single-threaded initialization
68-
* before any thread calls diags(). After initialization it is read-only;
69-
* concurrent reads are safe. Calling set() after initialization is
70-
* undefined behavior.
68+
* @thread-safety _diags_ptr is a plain (non-atomic) pointer. The first
69+
* call to set() must complete before any thread calls diags(). A second
70+
* call while other threads are actively calling diags() is a data race
71+
* on _diags_ptr; the behavior is formally undefined. In practice the
72+
* replacement window is narrow, but callers that cache the result of
73+
* diags() across a replacement may observe the previous instance.
7174
*/
7275
class DiagsPtr
7376
{
7477
public:
7578
friend Diags *diags();
7679

7780
/**
78-
* @brief Install the process-global Diags instance.
81+
* @brief Install or replace the process-global Diags instance.
7982
*
8083
* @param[in] new_ptr Pointer to a fully constructed Diags instance whose
8184
* lifetime exceeds all subsequent diags() callers. Ownership is NOT
8285
* transferred; the caller remains responsible for the object.
83-
* @pre new_ptr is non-null; no thread has called diags() since process
84-
* start (single-threaded initialization).
86+
* @pre new_ptr is non-null.
8587
* @post diags() returns new_ptr on all subsequent calls. Additionally,
8688
* DebugInterface::set_instance(new_ptr) is called, registering the Diags
8789
* instance as the active DebugInterface so that DbgCtl-based debug output
8890
* (Dbg / DbgPrint) is routed correctly.
8991
* @errors None signaled. A null new_ptr causes subsequent diags() calls
9092
* to return nullptr — a precondition violation.
91-
* @thread-safety Single-threaded initialization only. See class contract.
93+
* @thread-safety The first call must occur before other threads call
94+
* diags(). Subsequent calls while threads are active are a data race
95+
* on _diags_ptr; see class contract.
9296
*/
9397
static void set(Diags *new_ptr);
9498

include/tscore/DiagsTypes.h

Lines changed: 88 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ class DiagsConfigState
169169
* @param[in] dtt DiagsTagType_Debug or DiagsTagType_Action.
170170
* @param[in] new_value 0, 1, or 2 (see class contract).
171171
* @pre new_value is in [0, 2].
172-
* @post enabled(dtt) returns new_value on all subsequent calls.
172+
* @post enabled(dtt) returns new_value on all subsequent calls. As a side
173+
* effect, when dtt == DiagsTagType_Debug, DbgCtl::_config_mode is also
174+
* updated via a relaxed atomic store so that DbgCtl-based checks remain
175+
* in sync without acquiring a lock.
173176
* @errors None.
174177
* @thread-safety Must be called with external serialization against
175178
* concurrent enabled(dtt) reads.
@@ -195,19 +198,21 @@ class DiagsConfigState
195198
* via DiagsPtr.
196199
*
197200
* @thread-safety After construction, the instance is safe to use concurrently
198-
* from any number of threads for emission (print, log, error). Reconfiguration
199-
* methods (activate_taglist, deactivate_all, setup_diagslog, config_roll_*,
200-
* reseat_diagslog, should_roll_*, set_std_output) acquire an internal mutex
201-
* and are mutually exclusive with each other and with tag-table reads. See
202-
* per-method @thread-safety blocks for details.
201+
* from any number of threads for emission (print, log, error). Tag-table
202+
* mutation methods (activate_taglist, deactivate_all) and log-pointer
203+
* swap methods (setup_diagslog, reseat_diagslog, should_roll_*,
204+
* set_std_output) acquire tag_table_lock internally. Rolling-policy
205+
* configuration methods (config_roll_diagslog, config_roll_outputlog) and
206+
* dump() do NOT acquire any lock; see per-method @thread-safety for details.
203207
*/
204208
class Diags : public DebugInterface
205209
{
206210
public:
207211
/**
208212
* @brief Construct a Diags instance with the given initial configuration.
209213
*
210-
* @param[in] prefix_string Tag prefix prepended to all debug output.
214+
* @param[in] prefix_string Tag prefix prepended to all debug output. Must
215+
* be non-empty.
211216
* @param[in] base_debug_tags Initial debug tag regex, or nullptr for none.
212217
* @param[in] base_action_tags Initial action tag regex, or nullptr for none.
213218
* @param[in] _diags_log BaseLogFile for diags.log output, or nullptr to
@@ -216,10 +221,11 @@ class Diags : public DebugInterface
216221
* the system default (LOGFILE_DEFAULT_PERMS).
217222
* @param[in] output_log_perm File permission bits for stdout/stderr log
218223
* files, or -1 for the system default.
219-
* @pre None; safe to construct before any thread reads via diags().
224+
* @pre prefix_string is non-empty; safe to construct before any thread
225+
* reads via diags().
220226
* @post magic == DIAGS_MAGIC; tag tables are initialized from the given
221227
* regexes; diags_log is open if _diags_log is non-null and openable.
222-
* @errors Allocation failures abort via ink_fatal. Log-open failures are
228+
* @errors Allocation failures abort via ink_abort. Log-open failures are
223229
* reported via log_log_error and result in a null m_fp for the log.
224230
* @thread-safety Single-threaded construction expected. After construction
225231
* the instance may be installed via DiagsPtr::set and used concurrently.
@@ -290,13 +296,16 @@ class Diags : public DebugInterface
290296
* @brief Test whether the given IP endpoint matches the configured debug
291297
* client IP.
292298
*
293-
* @param[in] test_ip Endpoint to compare against debug_client_ip.
294-
* @return True if test_ip equals debug_client_ip.
299+
* @param[in] test_ip Endpoint whose IP address is compared against the
300+
* configured debug client IP. The port is ignored.
301+
* @return True if the IP address of @a test_ip equals the configured
302+
* debug client IP; false otherwise or if no debug client IP is set.
295303
* @pre None.
296304
* @post No state change.
297305
* @errors None.
298-
* @thread-safety Safe to call concurrently; debug_client_ip is written
299-
* only during single-threaded reconfiguration.
306+
* @thread-safety DEFECT: reads debug_client_ip, a plain (non-atomic)
307+
* struct, while the live config-update callback may write it on another
308+
* thread. Intended semantics: relaxed atomic read.
300309
*/
301310
bool
302311
test_override_ip(IpEndpoint const &test_ip)
@@ -393,15 +402,15 @@ class Diags : public DebugInterface
393402
/**
394403
* @brief Emit a message unconditionally, regardless of tag state.
395404
*
396-
* @param[in] tag Non-null C string label; included in output but not checked
397-
* against the tag regex.
398-
* @param[in] level DiagsLevel for routing and terminal-level handling.
405+
* @param[in] tag C string label included in output, or nullptr to omit the
406+
* tag prefix. Not checked against the tag regex.
407+
* @param[in] level DiagsLevel for sink routing.
399408
* @param[in] loc Source location of the call site, or nullptr.
400409
* @param[in] fmt Non-null printf-format string.
401410
* @pre fmt is non-null and arguments match its conversions.
402411
* @post Message emitted to all sinks enabled for level in config.outputs.
403-
* For terminal levels (DiagsLevel_IsTerminal), the process exits — this
404-
* call does not return.
412+
* Does not handle terminal levels; the process does not exit.
413+
* Use error_va() if terminal-level exit behavior is required.
405414
* @errors I/O errors during emission are absorbed; output may be lost.
406415
* @thread-safety Safe to call concurrently from any thread.
407416
*/
@@ -417,7 +426,7 @@ class Diags : public DebugInterface
417426
/**
418427
* @brief va_list form of print().
419428
*
420-
* @param[in] tag Non-null tag label.
429+
* @param[in] tag Tag label, or nullptr to omit the tag prefix.
421430
* @param[in] level DiagsLevel for routing.
422431
* @param[in] loc Source location, or nullptr.
423432
* @param[in] fmt Non-null printf-format string.
@@ -433,11 +442,12 @@ class Diags : public DebugInterface
433442
/**
434443
* @brief Emit a message only if the tag is active in DiagsTagType_Debug.
435444
*
436-
* @param[in] tag Non-null C string naming the debug tag.
445+
* @param[in] tag C string naming the debug tag, or nullptr (null matches
446+
* unconditionally; see tag_activated).
437447
* @param[in] level DiagsLevel for routing.
438448
* @param[in] loc Source location, or nullptr.
439449
* @param[in] fmt Non-null printf-format string.
440-
* @pre tag and fmt are non-null; arguments match fmt's conversions.
450+
* @pre fmt is non-null; arguments match fmt's conversions.
441451
* @post If on(tag) is true, message is emitted identically to print().
442452
* If on(tag) is false, no output is produced.
443453
* @errors Same as print().
@@ -458,16 +468,17 @@ class Diags : public DebugInterface
458468
/**
459469
* @brief va_list form of log().
460470
*
461-
* @param[in] tag Non-null tag name.
471+
* @param[in] tag Tag name, or nullptr (null matches unconditionally).
462472
* @param[in] level DiagsLevel for routing.
463473
* @param[in] loc Source location, or nullptr.
464474
* @param[in] fmt Non-null printf-format string.
465475
* @param[in] ap Initialized va_list. Consumed; caller MUST NOT reuse
466476
* without va_end + va_start.
467-
* @pre tag and fmt are non-null; ap is initialized.
477+
* @pre fmt is non-null; ap is initialized.
468478
* @post Same as log().
469479
* @errors Same as print().
470-
* @thread-safety Same as log().
480+
* @thread-safety Same as log(). The on() call reads _enabled without a
481+
* lock; see DiagsConfigState::enabled().
471482
*/
472483
void
473484
log_va(const char *tag, DiagsLevel level, const SourceLocation *loc, const char *fmt, va_list ap)
@@ -521,8 +532,10 @@ class Diags : public DebugInterface
521532
* @pre fp is a valid open stream.
522533
* @post Configuration summary written to fp.
523534
* @errors I/O errors on fp are not signaled.
524-
* @thread-safety Safe to call concurrently; acquires tag_table_lock
525-
* internally for the tag-table portion of output.
535+
* @thread-safety Not thread-safe. Reads config fields (_enabled,
536+
* outputs, base_debug_tags, base_action_tags) without holding
537+
* tag_table_lock. Concurrent reconfiguration may produce
538+
* interleaved or inconsistent output.
526539
*/
527540
void dump(FILE *fp = stdout) const;
528541

@@ -578,8 +591,10 @@ class Diags : public DebugInterface
578591
* @errors Open failures cause a false return and an internal log_log_error
579592
* diagnostic at LL_Error.
580593
* @thread-safety Caller MUST serialize with all other reconfiguration
581-
* methods. reseat_diagslog() acquires tag_table_lock around its call;
582-
* direct callers must provide equivalent serialization.
594+
* methods. reseat_diagslog() acquires tag_table_lock only for the
595+
* pointer swap that follows this call; setup_diagslog() itself is not
596+
* called under the lock. Direct callers must provide equivalent
597+
* serialization for the pointer swap.
583598
*/
584599
bool setup_diagslog(BaseLogFile *blf);
585600

@@ -591,10 +606,10 @@ class Diags : public DebugInterface
591606
* @param[in] rs Rolling size threshold in bytes (used by size-based
592607
* policies).
593608
* @pre None.
594-
* @post Rolling policy for diags.log is updated; effective on the next
595-
* should_roll_diagslog() call.
609+
* @post Rolling policy fields are updated in the calling thread.
596610
* @errors None.
597-
* @thread-safety Acquires tag_table_lock.
611+
* @thread-safety No lock is acquired. The caller must ensure no concurrent
612+
* calls to should_roll_diagslog() occur during reconfiguration.
598613
*/
599614
void config_roll_diagslog(RollingEnabledValues re, int ri, int rs);
600615

@@ -605,9 +620,10 @@ class Diags : public DebugInterface
605620
* @param[in] ri Rolling interval in seconds.
606621
* @param[in] rs Rolling size threshold in bytes.
607622
* @pre None.
608-
* @post Rolling policy for the output log is updated.
623+
* @post Rolling policy fields are updated in the calling thread.
609624
* @errors None.
610-
* @thread-safety Acquires tag_table_lock.
625+
* @thread-safety No lock is acquired. The caller must ensure no concurrent
626+
* calls to should_roll_outputlog() occur during reconfiguration.
611627
*/
612628
void config_roll_outputlog(RollingEnabledValues re, int ri, int rs);
613629

@@ -625,11 +641,10 @@ class Diags : public DebugInterface
625641
* FILE *).
626642
* 5. On failure: leaves the previous file in place.
627643
*
628-
* @return True if diags_log was successfully reseated; false if the
629-
* existing log was not initialized (the pre-init early-return path).
630-
* Note: the current implementation returns true even when
631-
* setup_diagslog() fails; actual failure is observable only via the
632-
* internal log_log_error message written to BaseLogFile's error channel.
644+
* @return False if diags_log is null or not yet initialized (safe no-op).
645+
* True in all other cases, including when the internal reopen fails —
646+
* reopen failures are not reflected in the return value and are
647+
* observable only via the internal diagnostic trace log.
633648
* @pre A Diags instance is active and diags_log is initialized
634649
* (is_init() == true). If this precondition is not met, returns false
635650
* without performing a reopen — this is the safe no-op path for calls
@@ -638,9 +653,9 @@ class Diags : public DebugInterface
638653
* path; the previous BaseLogFile (and its FILE *) is destroyed; all
639654
* subsequent emission goes to the new file.
640655
* On failure: diags_log is unchanged; the newly allocated BaseLogFile
641-
* is deleted by setup_diagslog() before it returns.
656+
* is deleted before it returns.
642657
* @errors Open failures are not directly signaled via the return value;
643-
* failure is observable only via an internal log_log_error message.
658+
* failure is reported to the internal diagnostic trace log only.
644659
* @thread-safety The swap in step 4 is performed under tag_table_lock.
645660
* Concurrent emission either observes the pre-swap or post-swap log;
646661
* no message is lost or written to a destroyed FILE *.
@@ -652,42 +667,55 @@ class Diags : public DebugInterface
652667
bool reseat_diagslog();
653668

654669
/**
655-
* @brief Test whether diags.log is due for a roll under its current policy.
670+
* @brief Roll diags.log if the current rolling policy condition is met.
656671
*
657-
* @return True if the configured rolling condition is met.
672+
* @return True if the underlying file was renamed (rolled); false if no
673+
* roll condition was triggered, or if fstat failed. Note: true is
674+
* returned even when the subsequent reopen of the new log file fails.
658675
* @pre None.
659-
* @post No state change; rolling is not performed.
660-
* @errors None.
661-
* @thread-safety Acquires tag_table_lock.
676+
* @post If the rolling condition is met: the current diags.log is flushed,
677+
* rolled (renamed), and replaced by a new BaseLogFile at the same path.
678+
* If not: no state change. fstat failure causes an early false return
679+
* without rolling.
680+
* @errors None signaled. fstat and reopen failures silently suppress the
681+
* replacement; the rolled state of the original file is not reversed.
682+
* @thread-safety tag_table_lock is acquired only during the BaseLogFile
683+
* pointer swap. The rolling-condition checks and file operations execute
684+
* without a lock; the caller must ensure no concurrent reconfiguration
685+
* (see config_roll_diagslog()).
662686
*/
663687
bool should_roll_diagslog();
664688

665689
/**
666-
* @brief Test whether the output log is due for a roll under its current
667-
* policy.
690+
* @brief Roll stdout_log and stderr_log if the current rolling policy
691+
* condition is met.
668692
*
669-
* @return True if the configured rolling condition is met.
670-
* @pre None.
671-
* @post No state change; rolling is not performed.
672-
* @errors None.
673-
* @thread-safety Acquires tag_table_lock.
693+
* @return True if any output log was rolled; false otherwise.
694+
* @pre stdout_log and stderr_log are non-null.
695+
* @post If the rolling condition is met: affected logs are flushed, rolled,
696+
* and replaced by new BaseLogFile instances at the same paths.
697+
* If not: no state change. fstat failure causes an early false return.
698+
* @errors None signaled. fstat failures silently suppress the roll.
699+
* @thread-safety Same as should_roll_diagslog(); see config_roll_outputlog().
674700
*/
675701
bool should_roll_outputlog();
676702

677703
/**
678704
* @brief Reseat the named standard stream to a file at the given path.
679705
*
680706
* @param[in] stream STDOUT or STDERR (see StdStream).
681-
* @param[in] file Non-null, non-empty path string. Passed verbatim to the
682-
* OS open call; symlinks are re-resolved at each call. An empty string
683-
* causes an immediate false return without modifying state.
684-
* @return True on success; false if the file could not be opened or the
685-
* resulting FILE * is null.
686-
* @pre A Diags instance is active; file is non-null and non-empty.
707+
* @param[in] file Non-null path string. Passed verbatim to the OS open
708+
* call; symlinks are re-resolved at each call. An empty string causes an
709+
* immediate false return without modifying any state.
710+
* @return True on success; false if file is empty, the file could not be
711+
* opened, or the resulting FILE * is null.
712+
* @pre A Diags instance is active; file is non-null (passing null is UB —
713+
* it is passed directly to strcmp without a null guard).
687714
* @post On success: the new file is open and bound as the named stream;
688715
* the previous BaseLogFile is deleted.
689-
* On failure: the previous BaseLogFile is deleted and the stream pointer
690-
* is set to nullptr — the previous binding is NOT preserved.
716+
* On failure: the stream pointer is set to nullptr — the previous
717+
* binding is NOT preserved. The previous BaseLogFile is NOT freed
718+
* (leaked on the failure path).
691719
* @errors File-open failures cause a false return and internal
692720
* log_log_error messages; the named stream is left unbound (nullptr).
693721
* @thread-safety Acquires tag_table_lock internally on both success and

include/tscore/LogMessage.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,22 @@ class LogMessage : public Throttler
167167
* @brief Emit a tagged message at the given level using Diags::print,
168168
* subject to throttling.
169169
*
170+
* Unlike message(), this method delegates to Diags::print_va, which does
171+
* not perform terminal-level exit. The process will not exit regardless of
172+
* level; use message() if terminal-level exit behavior is required.
173+
*
170174
* @param[in] tag Non-null tag string (included in output, not checked
171175
* against the tag regex).
172176
* @param[in] level DiagsLevel for routing.
173177
* @param[in] loc Call-site source location.
174178
* @param[in] fmt Non-null printf-format string.
175179
* @pre tag and fmt are non-null.
176-
* @post Same as message() except throttling uses the debug throttling
177-
* interval (_default_debug_throttling_interval) rather than the log
178-
* throttling interval (_default_log_throttling_interval).
179-
* @errors None signaled.
180+
* @post If not throttled: message emitted to all sinks enabled for level.
181+
* The process does not exit, even for terminal levels.
182+
* If throttled: message silently dropped.
183+
* Throttling uses the debug throttling interval
184+
* (_default_debug_throttling_interval).
185+
* @errors None signaled; I/O errors absorbed.
180186
* @thread-safety Safe to call concurrently.
181187
*/
182188
void print(const char *tag, DiagsLevel level, SourceLocation const &loc, const char *fmt, ...) TS_PRINTFLIKE(5, 6);

src/tscore/Diags.cc

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -703,12 +703,6 @@ Diags::should_roll_outputlog()
703703
return ret_val;
704704
}
705705

706-
/*
707-
* Sets up a BaseLogFile for the specified file. Then it binds the specified standard steam
708-
* to the aforementioned BaseLogFile.
709-
*
710-
* Returns true on successful binding and setup, false otherwise
711-
*/
712706
bool
713707
Diags::set_std_output(StdStream stream, const char *file)
714708
{

0 commit comments

Comments
 (0)