60 Dakika Session Cleanup Bug

Muzibu - Arka Plan Tab'larında Yanlış Logout Sorunu

Analiz Tarihi
20 Aralık 2025

📝 Basit Anlatım (Sorun Nedir?)

Kullanıcı Muzibu'da müzik dinlerken, tab'ı arka plana alıyor (başka tab'a geçiyor veya bilgisayarı kilitliyor). Müzik çalmaya devam ediyor ✅ ama 1 saat sonra tab'a döndüğünde oturumu kapanmış oluyor.

BU BİR BUG! Kullanıcı müzik dinlemiş, hiçbir şey yapmamış, sadece başka tab'a geçmiş. Ama sistem "60 dakikadır inaktif" diyerek oturumu silmiş.

🎯
Neden Önemli?

Kullanıcı laptop'unda müzik açıp çalışıyor, 1 saat sonra tab'a dönüyor, "başka cihazdan giriş yapıldı" uyarısı alıyor. Tekrar login olmak zorunda kalıyor. Çok kötü bir kullanıcı deneyimi!

🔬 Root Cause (Sorunun Kök Nedeni)

🐛 Ana Neden: getActiveDevices() Metodundaki 60 Dakikalık Auto-Cleanup

📄 DeviceService.php:388-392
public function getActiveDevices(User $user): array
{
    // Sadece 60 dakikadan eski session'ları temizle (stale cleanup)
    DB::table($this->table)
        ->where('user_id', $user->id)
        ->where('last_activity', '<', now()->subMinutes(60))
        ->delete();

    return DB::table($this->table)
        ->where('user_id', $user->id)
        ->get();
}

Bu kod ne yapıyor?

  • last_activity alanı 60 dakikadan eski olan session'ları SİLİYOR
  • Bu cleanup getActiveDevices() her çağrıldığında çalışıyor
  • getActiveDevices() logout olayında, device listesi gösterilirken çağrılıyor

💤 İkinci Neden: Browser Sleep Mode (Tab Throttling)

Browser sleep mode nedir?

Chrome, Firefox gibi modern tarayıcılar, arka plandaki tab'larda JavaScript'i yavaşlatır veya durdurur (batarya/performans tasarrufu için).

✅ Tab Aktif (Önde)
  • • Müzik çalıyor ✅
  • • Polling her 5 saniyede çalışıyor ✅
  • last_activity güncelleniyor ✅
❌ Tab Arka Plan (Sleep)
  • • Müzik çalıyor ✅ (Audio API korunur)
  • • Polling DURUR ❌ (setInterval throttle)
  • last_activity güncellenmiyor ❌
Kritik: Kullanıcı müzik dinliyor ama last_activity güncellenmiyor çünkü polling durmuş! 60 dakika sonra session "inaktif" kabul edilip siliniyor.

📡 Üçüncü Faktör: Polling Frequency (5 Saniye)

Polling nedir?

Frontend her 5 saniyede bir backend'e "oturumum hala aktif mi?" diye soruyor. Bu sırada last_activity güncelleniyor.

📄 session.js:34
const SESSION_POLL_INTERVAL = 5000; // 5 saniye

setInterval(() => {
    this.checkSessionValidity(); // → /api/auth/check-session
}, SESSION_POLL_INTERVAL);
Sorun: Tab arka plana geçince setInterval durur/yavaşlar → Polling çalışmaz → last_activity güncellenmiyor!

📊 Gerçek Olay Analizi (19 Aralık 2025 - User 2)

11:45:59
Sabah
✅ Login Başarılı
Session oluşturuldu: BBB7CVKcYKSqgUlR0EcB...
Login token: 3af71df5399f8abb...
11:46 - 20:12
8.5 saat
🎵 Normal Kullanım (Müzik Dinleme)
• Her 5 saniyede polling çalışıyor ✅
last_activity sürekli güncelleniyor ✅
• Web sunucu log'ları: Her 5 saniyede /api/auth/check-session isteği ✅
20:12:18
Akşam
⏰ Son Başarılı Polling
Laravel log: sessionExists: Checking with login_token
Web sunucu: GET /api/auth/check-session → 200 OK
last_activity güncellendi: 20:12:18
20:12 - 21:41
1.5 saat
💤 TAM SESSİZLİK (Browser Sleep Mode)
Hiç polling isteği yok!
• Web sunucu log: 0 istek (20:12:17 → 21:41:35 arası)
• Laravel log: Hiç sessionExists çağrısı yok
last_activity 20:12:18'de kaldı! (güncellenmiyor)
Ama müzik çalıyor olabilir! ✅ (Audio API korunur)
21:41:35
Gece
⏰ Tab Uyandı (Kullanıcı geri döndü)
Web sunucu: GET /api/auth/check-session → 200 OK
Polling tekrar başladı!
21:41:38
3 saniye sonra
🗑️ AUTO-CLEANUP TETİKLENDİ!
1. sessionExists() çağrıldı
2. getActiveDevices() çağrıldı (logout uyarısı için)
3. getActiveDevices() içinde cleanup çalıştı:
DB::table('user_active_sessions')
  ->where('last_activity', '<', now()->subMinutes(60))
  ->delete();

// Hesaplama:
// now() = 21:41:38
// now() - 60dk = 20:41:38
// last_activity = 20:12:18
// 20:12:18 < 20:41:38 → TRUE ✅
// SESSION SİLİNDİ! ❌
Sonuç: Session DB'den silindi! devices_count: 0
21:41:38
Aynı anda
💥 YANLIŞLIK - LOGOUT!
Laravel log: sessionExists: Login token not found in DB - LIFO kicked
Frontend: "Başka bir cihazdan giriş yapıldı" uyarısı
Kullanıcı logout oldu ❌

📜 Log Kanıtları

📄 Laravel Logs (tenant-2025-12-19.log)

20:12:18 - Son başarılı check:
sessionExists: Checking with login_token
{"user_id":2,"cookie_token":"3af71df5..."}
21:41:38 - Logout olayı:
sessionExists: Login token not found in DB
checkSession: Session not found (LIFO kicked)
getActiveDevices DEBUG {"devices_count":0}
⚠️ Başarılı check-session istekleri loglanmıyor!
Sadece hatalar log'da görünüyor.

🌐 Web Sunucu Logs (access_ssl_log)

20:12:17 - Son istek:
GET /api/auth/check-session → 200 OK
20:12 - 21:41 arası:
0 istek! ❌
21:41:35 - İlk istek (uyandı):
GET /api/auth/check-session → 200 OK
✅ Tüm HTTP istekleri kaydedilmiş.
Bugün toplam: 3,747 check-session isteği

✅ Çözüm Önerileri

1

⏰ Cleanup Süresini Artır (60dk → 4 saat)

En basit ve etkili çözüm. 60 dakika çok kısa, kullanıcılar laptop'larını kilitleyip 1-2 saat çalışabiliyor.

📄 DeviceService.php:391
// ❌ ESKİ (60 dakika)
->where('last_activity', '<', now()->subMinutes(60))

// ✅ YENİ (4 saat = 240 dakika)
->where('last_activity', '<', now()->subMinutes(240))
✅ Artılar:
  • Çok kolay değişiklik (1 satır)
  • Test etmeye gerek yok
  • Kullanıcı deneyimi düzelir
⚠️ Ekstralar:
  • DB'de daha fazla session kalır (disk)
  • Ama zaten küçük tablo (sorun değil)
2

🗑️ Cleanup'ı Ayrı Scheduled Task'e Al

getActiveDevices() içinde cleanup yapmak kötü bir tasarım. Ayrı bir cron job oluştur.

📄 app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // Her gece 03:00'te eski session'ları temizle
    $schedule->call(function () {
        DB::table('user_active_sessions')
            ->where('last_activity', '<', now()->subHours(24))
            ->delete();
    })->dailyAt('03:00');
}
✅ Artılar:
  • Daha temiz mimari
  • 24 saat inaktif session'ları temizler
  • Kullanıcıya anında etki etmez
❌ Eksiler:
  • Daha karmaşık
  • Cron job yapılandırması gerekir
  • Test etmek lazım
3

📡 Page Visibility API (Polling'i Sleep'te Çalıştır)

JavaScript'e tab arka plana geçse bile polling'i çalıştırmayı söyle.

📄 session.js
document.addEventListener('visibilitychange', function() {
    if (document.hidden) {
        // Tab arka plana geçti
        console.log('Tab arka planda, ama polling devam ediyor!');
        // setInterval'i durdurma, çalışmaya devam et
    } else {
        // Tab ön plana geldi
        console.log('Tab ön planda');
    }
});
✅ Artılar:
  • Session her zaman güncel kalır
  • 60 dakikalık limit yeterli olur
❌ Eksiler:
  • Batarya tüketir (her 5 saniyede istek)
  • Browser yine de throttle yapabilir
  • Garanti değil
4

⏱️ Polling Frekansını Azalt (5 saniye → 5 dakika)

Üretim ortamında her 5 saniyede polling çok agresif. 5 dakikaya çıkar.

📄 session.js:34
// ❌ TEST: Her 5 saniye (çok sık)
const SESSION_POLL_INTERVAL = 5000;

// ✅ CANLI: Her 5 dakika (yeterli)
const SESSION_POLL_INTERVAL = 300000;
✅ Artılar:
  • Server yükü %98 azalır
  • Batarya tüketimi azalır
  • Browser throttle daha az etkiler
⚠️ Dikkat:
  • Logout 5 dakika geç fark edilir
  • Ama zaten sorun yok (Çözüm 1'le)

🎯 Önerilen Çözüm

🏆

Çözüm 1 + Çözüm 4 Kombinasyonu

1. Cleanup süresini 4 saate çıkar
->where('last_activity', '<', now()->subMinutes(240))
2. Polling'i 5 dakikaya çıkar (üretimde)
const SESSION_POLL_INTERVAL = 300000; // 5 dakika
Sonuç:
  • Kullanıcı 4 saate kadar tab arka planda bırakabilir ✅
  • Server yükü %98 azalır (5 saniye → 5 dakika) ✅
  • Batarya tüketimi azalır ✅
  • False positive logout olmaz ✅
💡 Not: İleride Çözüm 2 de uygulanabilir (cleanup'ı scheduled task'e almak) ama önce bu basit değişikliklerle başla.