diff --git a/Mimante/Engine/AuctionRunner.cs b/Mimante/Engine/AuctionRunner.cs
index aff5070..9395021 100644
--- a/Mimante/Engine/AuctionRunner.cs
+++ b/Mimante/Engine/AuctionRunner.cs
@@ -164,22 +164,28 @@ namespace AutoBidder.Engine
private int NextPollDelay(AppSettings settings)
{
// Un'asta non ancora cominciata ha una cadenza tutta sua: vedi NotStartedDelay.
+ // È l'unico caso rimasto in cui si aspetta, e non è una scelta di ritmo: fino
+ // all'apertura il server non ha niente di diverso da dire, e interrogarlo
+ // seicento volte al minuto per tre ore non anticipa l'inizio di un istante.
var notStarted = NotStartedDelay(settings);
if (notStarted > 0) return notStarted;
- var remaining = _auction.EstimatedRemainingMs();
-
- if (remaining == double.MaxValue) return settings.PollIntervalFarMs;
-
- // In sola osservazione non c'è puntata da azzeccare: si risparmiano chiamate.
- var critical = _auction.State == RunState.Active
- ? settings.PollIntervalCriticalMs
- : settings.PollIntervalNearMs;
-
- if (remaining <= settings.CriticalWindowMs) return critical;
- if (remaining <= 10_000) return settings.PollIntervalNearMs;
- if (remaining <= 60_000) return settings.PollIntervalMidMs;
- return settings.PollIntervalFarMs;
+ // ── Nessuna attesa: la cadenza la detta la rete ──────────────────────
+ //
+ // Prima qui c'erano quattro soglie configurabili (lontano / medio / vicino /
+ // critico). Sui registri di tre giorni non hanno mai funzionato come scritto:
+ // con 40 richieste al secondo di tetto globale spartite fra tutte le aste
+ // seguite, i 220 ms della finestra critica diventavano 596 ms misurati, e con
+ // il timer a un secondo restava un solo sguardo prima della scadenza.
+ //
+ // Il ritmo adesso è quello del giro di andata e ritorno: si riparte appena la
+ // risposta è arrivata. Con ping ~57 ms misurato sono circa diciassette
+ // interrogazioni al secondo per asta.
+ //
+ // Il freno non è più un numero scelto a tavolino ma la risposta del server:
+ // vedi la reazione a 429 e 503 in PollLoopAsync. Se Bidoo dice di rallentare,
+ // si rallenta; finché non lo dice, si va al massimo che regge la rete.
+ return 0;
}
///
diff --git a/Mimante/Modifiche.txt b/Mimante/Modifiche.txt
index 90c531e..e69de29 100644
--- a/Mimante/Modifiche.txt
+++ b/Mimante/Modifiche.txt
@@ -1,8 +0,0 @@
-Cose da fare:
- - Non caricare i sorgenti del programma nella release. Deve solo essere presente la versione portable e la versione installabile.
- - Il nome del setup deve essere AutoBidder_Versione.exe (ad esempio AutoBidder_4.14.0.exe)
- - Colora il pulsante stop di rosso (così come quello verde è verde) per quanto riguarda i globali. Colora anche quelli relativi alla singola asta presenti sulla griglia di monitoraggio AutoBidder_Versione
- - Verifica tutta l'applicazione e tutto il codice per cercare di ottimizzare l'applicazione per renderla più veloce e meno bisognosa di risorse
- - Aggiungi la possibilità di pulire le aste non valide o con non abbastanza dati, come per esempio quelle non seguite completamente dall'inizio alla fine. In generale devo poterle pulire
- - Agiungi una impostazione per indicare il costo di una puntata (fisso a 20 centesimi, che corrisponde al piano meno vantaggioso per l'acquisto di puntate dal sito di Bidoo)
-
\ No newline at end of file
diff --git a/Mimante/Net/BidooHttpClient.cs b/Mimante/Net/BidooHttpClient.cs
index af9041c..d491477 100644
--- a/Mimante/Net/BidooHttpClient.cs
+++ b/Mimante/Net/BidooHttpClient.cs
@@ -111,6 +111,53 @@ namespace AutoBidder.Net
/// Aggiorna i limiti di ritmo a caldo, senza ricreare il trasporto.
public void Configure(double requestsPerSecond) => _limiter.SetRate(requestsPerSecond);
+ // ── Freno dettato dal server ─────────────────────────────────────────
+ //
+ // Senza tetto fisso serve qualcosa che reagisca, altrimenti l'unico segnale di
+ // "troppe richieste" sarebbe un blocco dell'account. Un 429 o un 503 mettono in
+ // pausa il polling per un secondo, raddoppiando fino a otto se insistono, e la
+ // pausa si azzera al primo giro andato bene.
+ //
+ // Le puntate hanno priorità Critical e non passano da qui: se il momento giusto
+ // è adesso, un freno preventivo costerebbe l'asta.
+
+ private const int FrenoMinimoMs = 1000;
+ private const int FrenoMassimoMs = 8000;
+
+ private long _frenoFinoA;
+ private int _frenoCorrenteMs = FrenoMinimoMs;
+
+ /// Millisecondi di pausa ancora da scontare. Zero quando si va liberi.
+ public int FrenoResiduoMs
+ {
+ get
+ {
+ var mancano = (Interlocked.Read(ref _frenoFinoA) - Stopwatch.GetTimestamp())
+ * 1000 / Stopwatch.Frequency;
+ return mancano > 0 ? (int)mancano : 0;
+ }
+ }
+
+ private void Frena()
+ {
+ var durata = Volatile.Read(ref _frenoCorrenteMs);
+
+ Interlocked.Exchange(ref _frenoFinoA,
+ Stopwatch.GetTimestamp() + durata * Stopwatch.Frequency / 1000);
+
+ Volatile.Write(ref _frenoCorrenteMs, Math.Min(FrenoMassimoMs, durata * 2));
+ }
+
+ private void Rilascia() => Volatile.Write(ref _frenoCorrenteMs, FrenoMinimoMs);
+
+ private ValueTask RispettaFrenoAsync(CancellationToken ct)
+ {
+ var mancano = FrenoResiduoMs;
+ return mancano <= 0
+ ? ValueTask.CompletedTask
+ : new ValueTask(Task.Delay(mancano, ct));
+ }
+
/// Costruisce una GET già completa di header di autenticazione e di contesto.
public HttpRequestMessage BuildGet(string url, string? referer = null, bool ajax = true)
{
@@ -206,6 +253,7 @@ namespace AutoBidder.Net
if (!critical)
{
+ await RispettaFrenoAsync(ct).ConfigureAwait(false);
await _limiter.WaitAsync(ct).ConfigureAwait(false);
await _concurrency.WaitAsync(ct).ConfigureAwait(false);
}
@@ -224,6 +272,12 @@ namespace AutoBidder.Net
if (!response.IsSuccessStatusCode) Interlocked.Increment(ref _requestsFailed);
+ // Tolto il tetto fisso, il freno lo dà il server. 429 e 503 sono il modo in
+ // cui dice "stai chiedendo troppo": si smette per un attimo invece di
+ // insistere finché non arriva un blocco vero. Le puntate non passano di qui.
+ if ((int)response.StatusCode is 429 or 503) Frena();
+ else if (response.IsSuccessStatusCode) Rilascia();
+
return new HttpOutcome
{
Success = response.IsSuccessStatusCode,
@@ -304,9 +358,20 @@ namespace AutoBidder.Net
private double _tokens;
private long _lastTicks;
+ ///
+ /// Zero o negativo = nessun limite: il secchiello si toglie di mezzo del tutto.
+ ///
+ /// Serve perché il tetto globale era il vero collo di bottiglia. Misurato sui
+ /// registri: con 24 aste seguite insieme e 40 richieste al secondo di tetto, la
+ /// distribuzione delle richieste era tagliata netta sul limite (p90 39, p99 41) e
+ /// ogni asta finiva interrogata ogni ~596 ms invece dei 220 configurati — proprio
+ /// negli ultimi secondi, dove quel ritardo costa l'asta.
+ ///
+ private bool Illimitato => _ratePerSecond <= 0;
+
public TokenBucket(double ratePerSecond, int capacity)
{
- _ratePerSecond = Math.Max(1, ratePerSecond);
+ _ratePerSecond = ratePerSecond;
_capacity = Math.Max(1, capacity);
_tokens = _capacity;
_lastTicks = Stopwatch.GetTimestamp();
@@ -316,8 +381,8 @@ namespace AutoBidder.Net
{
lock (_sync)
{
- _ratePerSecond = Math.Max(1, ratePerSecond);
- _capacity = Math.Max(8, _ratePerSecond);
+ _ratePerSecond = ratePerSecond;
+ _capacity = Math.Max(8, Math.Abs(_ratePerSecond));
if (_tokens > _capacity) _tokens = _capacity;
}
}
@@ -328,6 +393,8 @@ namespace AutoBidder.Net
lock (_sync)
{
+ if (Illimitato) return ValueTask.CompletedTask;
+
var now = Stopwatch.GetTimestamp();
var elapsedSeconds = (now - _lastTicks) / (double)Stopwatch.Frequency;
_lastTicks = now;
diff --git a/Mimante/Utilities/SettingsManager.cs b/Mimante/Utilities/SettingsManager.cs
index 206ca83..c148212 100644
--- a/Mimante/Utilities/SettingsManager.cs
+++ b/Mimante/Utilities/SettingsManager.cs
@@ -82,7 +82,18 @@ namespace AutoBidder.Utilities
/// Tetto complessivo di richieste al secondo verso Bidoo (le puntate non sono soggette
/// al limite: hanno corsia preferenziale). Default: 40.
///
- public double MaxRequestsPerSecond { get; set; } = 40;
+ ///
+ /// Tetto di richieste al secondo verso Bidoo. 0 = nessun tetto, ed è il
+ /// predefinito: il ritmo lo detta la rete, e a frenare è il server con 429/503.
+ ///
+ /// Il vecchio predefinito era 40, ed era il vero collo di bottiglia. Misurato
+ /// sui registri di tre giorni: con fino a 31 aste seguite insieme la distribuzione
+ /// delle richieste era tagliata netta sul tetto (p90 39, p99 41), e ogni asta
+ /// veniva interrogata ogni ~596 ms invece dei 220 configurati per la finestra
+ /// critica. Con il timer a un secondo restava un solo sguardo prima della
+ /// scadenza: da lì le puntate perse e il tempo che non scorreva.
+ ///
+ public double MaxRequestsPerSecond { get; set; } = 0;
///
/// Porta la granularità del timer di sistema a 1 ms mentre il monitor è in funzione.