// EXERCISE 02 — BROKEN STARTING POINT.  Do not edit this file; copy it.
//
// ============================ UNSAFE CODE WARNING ============================
// DownloadStats below contains a DELIBERATE data race. It is marked
// `@unchecked Sendable` purely to stop Swift 6 strict concurrency checking from
// rejecting it, so the bug can be demonstrated. That annotation is a promise to
// the compiler that you have synchronised the type yourself; here the promise
// is a lie, on purpose. Never write @unchecked Sendable to quiet a diagnostic.
// =============================================================================
//
// THE SCENARIO
//   A download manager tallies bytes and chunks from several transfer threads.
//   The totals shown in the UI are "a bit low, but only on fast connections".
//
// Build (plain):  swiftc -swift-version 6 -Onone stats.swift -o stats_broken
// Build (TSan):   swiftc -swift-version 6 -Onone -g -sanitize=thread \
//                        stats.swift -o stats_broken_tsan
// Run:            ./stats_broken
//
// Expected: the plain build prints correct=false and a nonzero `lost` count
// that CHANGES BETWEEN RUNS. The sanitized build additionally prints
// "WARNING: ThreadSanitizer: ..." on stderr. Exit status is 0 either way — a
// data race is not a crash, which is exactly what makes it dangerous.
//
// Bounded: fixed iteration counts, no blocking waits, plus a watchdog thread
// that force-exits after WATCHDOG_S.

import Foundation

let WATCHDOG_S: Double = 60
let EXIT_WATCHDOG: Int32 = 75

/// The reason this fixture cannot stall a validation run.
func armWatchdog(_ seconds: Double, _ label: String) {
    let t = Thread {
        Thread.sleep(forTimeInterval: seconds)
        fputs("\nWATCHDOG: \(label) exceeded \(seconds)s — forcing _exit(\(EXIT_WATCHDOG)).\n", stderr)
        fflush(stderr)
        _exit(EXIT_WATCHDOG)
    }
    t.stackSize = 512 * 1024
    t.start()
}

// MARK: - the shared tally

/// Two counters updated from every transfer thread.
///
/// `bytesReceived += bytes` is not one instruction. It is a load, an add and a
/// store, and nothing here stops a second thread from loading the same value
/// between our load and our store. That second thread's update is then
/// overwritten and gone.
final class DownloadStats: @unchecked Sendable {      // <-- UNSAFE BY DESIGN
    var bytesReceived = 0
    var chunksReceived = 0

    func record(bytes: Int) {
        bytesReceived += bytes
        chunksReceived += 1
    }

    func snapshot() -> (bytes: Int, chunks: Int) {
        (bytesReceived, chunksReceived)
    }
}

// MARK: - the workload

let threads = 8
let perThread = 200_000
let chunkBytes = 4

func runTransfers() -> (bytes: Int, chunks: Int) {
    let stats = DownloadStats()
    let group = DispatchGroup()
    for _ in 0..<threads {
        DispatchQueue.global(qos: .userInitiated).async(group: group) {
            for _ in 0..<perThread {
                stats.record(bytes: chunkBytes)
            }
        }
    }
    group.wait()
    return stats.snapshot()
}

armWatchdog(WATCHDOG_S, "EX02 build=broken")

let expectedChunks = threads * perThread
let expectedBytes = expectedChunks * chunkBytes
let observed = runTransfers()

print("EX02 build=broken threads=\(threads) perThread=\(perThread) "
    + "expectedChunks=\(expectedChunks) observedChunks=\(observed.chunks) "
    + "lostChunks=\(expectedChunks - observed.chunks) "
    + "expectedBytes=\(expectedBytes) observedBytes=\(observed.bytes) "
    + "correct=\(observed.chunks == expectedChunks && observed.bytes == expectedBytes)")
