Perf(license): mutex + write-suppression for licenses.ini hot path
licenses.ini was hit on every heartbeat -- 5s x clients x ~8 SetStr per
auth -- with no concurrency protection. Two consequences:
1. 100 concurrent online would saturate the file (~160 writes/sec,
full-file rewrite each via WritePrivateProfileString).
2. Concurrent SetPendingRenewal / DecrementPendingQuota with no lock
occasionally clobbered freshly-set renewal quotas (reported by
user as "preset renewal silently disappears").
Add LicensesIniMutex() (Meyers singleton recursive_mutex, exposed in
CPasswordDlg.h so both CPasswordDlg.cpp and CLicenseDlg.cpp share it)
and wrap all 15 functions that touch licenses.ini.
Rewrite UpdateLicenseActivity around g_activityCache (in-memory state
keyed by "SN|IP|machine"): skip the entire write path when nothing
changed and the 30s LastActiveTime throttle window hasn't expired.
Passcode/HMAC are only flushed on actual change (renewal path); IP
list is only rewritten when the yyMMdd timestamp would roll a day.
Measured impact (local 2-client baseline):
before: 0.60 writes/sec (4 writes per heartbeat cluster)
after: 0.07 writes/sec (one write per client per 30s throttle)
Extrapolated to the 100-online target:
before: ~160 writes/sec (saturation)
after: ~3.3 writes/sec (100 clients / 30s throttle window)
Race elimination is the more important win: PendingQuota's
read-modify-write is now atomic, so the "preset renewal disappears"
race is closed.
Notes from audit (these landed during the same iteration):
- Cache key is (SN, IP, machine), not SN alone. A single SN can be
shared by 100+ end machines in bulk-license deployments, so a
per-SN cache flips on every heartbeat and defeats suppression.
Per-(SN, IP, machine) throttling is what makes the 100/30 model
actually hold; an SN-only key reproduced the original ~0.7 writes/s.
- DeleteLicense invalidates the per-SN activity cache via
InvalidateLicenseActivityCache() (prefix scan since one SN maps to
many cache entries). Without this, cache hits after delete would
skip the auto-recreate path and leave the section permanently
missing.
- OnLicenseViewIPs: m_ListLicense.SetItemText moved outside the lock
so the critical section only covers disk I/O.
This commit was merged in pull request #2.
This commit is contained in:
@@ -2,10 +2,23 @@
|
||||
|
||||
#include <afx.h>
|
||||
#include <afxwin.h>
|
||||
#include <mutex>
|
||||
#include "Resource.h"
|
||||
#include "common/commands.h"
|
||||
#include "LangManager.h"
|
||||
|
||||
// 全局 licenses.ini 互斥锁(Meyers singleton,跨翻译单元共享)。
|
||||
// 所有读写 licenses.ini 的函数入口必须加锁,否则在心跳并发下会出现
|
||||
// read-modify-write 丢更新(典型受害者:PendingQuota / IP 列表)。
|
||||
// 使用 recursive_mutex 是因为部分函数会嵌套调用(如 DecrementPendingQuota → ClearPendingRenewal)。
|
||||
std::recursive_mutex& LicensesIniMutex();
|
||||
|
||||
// 让 UpdateLicenseActivity 内部缓存里某个 SN 的 entry 失效。
|
||||
// 必须在外部修改了授权(删除 / 重新创建 section)后调用,否则 cache 命中策略
|
||||
// 会跳过本应触发的"既往授权自动加入"路径,导致 disk 上的 section 不会重建。
|
||||
// 实现在 CPasswordDlg.cpp,需持 LicensesIniMutex(内部会自行加锁,可在已加锁线程嵌套调用)。
|
||||
void InvalidateLicenseActivityCache(const std::string& deviceID);
|
||||
|
||||
// CPasswordDlg 对话框
|
||||
namespace TcpClient {
|
||||
std::string ObfuscateAuthorization(const std::string& auth);
|
||||
|
||||
Reference in New Issue
Block a user