Help with Singleton Not Correctly Referencing

My initial assumption was a race condition. So I went down the rabbit hole and assumed race conditions were happening. So I implemented a mutex lock with the singleton. I needed either way as Im managing many mobs. So as a kind thank you heres how to implement a mutex lock hashmap using a Signals library and a singleton!

--!nonstrict
--ServerScriptService.MobSystem.MobStatManager.lua 
--[[
	Stores all mob instances and stats
]]
local Signal = require(script.GoodSignal) 

local MobStatManager = {}
MobStatManager.__index = MobStatManager

-- Define the type for the params table
type MobParams = {
	maxHealth: number?,
	health: number?,
	attack: number?
}

-- Define the type for the mobStats table
type MobStats = {
	[Instance]: {
		maxHealth: number,
		health: number,
		attack: number
	}
}

export type TypeMobStatManager = {
	mobStats: MobStats,
	registerMob: (self: TypeMobStatManager, mob: Instance, params: MobParams?) -> (),
	isDead: (self: TypeMobStatManager, mob: Instance) -> boolean,
	takeDamage: (self: TypeMobStatManager, mob: Instance, damage: number) -> boolean
}

-- Default mob stats
local DEFAULT_MOB_STATS = {
	maxHealth = 100,
	health = 100,     
	attack = 10
} :: MobParams

-- Attributes for syncing with the client
local ATTRIBUTES = {
	HEALTH = "Health",
	MAX_HEALTH = "MaxHealth"
}

local STATES = {
	ISDEAD = "IsDead",
}

local _instance: TypeMobStatManager = nil
local _mobStats = {} -- Stores mob stats
local _mutexTable = {} -- Stores lock signals per mob

--[[ Private function: Acquires a lock for a mob 
	Using counter/semaphore to avoid deadlocks in the edge case the same mob is locked at the same time
	
	@PARAMS mob - key to the mutex table reference
]]
function MobStatManager._acquireLock(mob: Instance)
	if not mob then return end

	-- Initialize the lock if it doesn't exist
	if not _mutexTable[mob] then
		_mutexTable[mob] = {
			signal = Signal.new(),
			count = 0
		}
	end

	local mutex = _mutexTable[mob]

	-- Wait until the lock is free
	while mutex.count > 0 do
		mutex.signal:Wait() -- Wait for the signal
		task.wait() -- Yield to prevent CPU burning
		print("waiting wooo")
	end

	-- Acquire the lock
	mutex.count += 1
end

--[[ Private function: 
	Releases a lock for a mob and unblocks any other tasks waiting to acquirelock

	@PARAM mob - key to the mutex table reference
]]
function MobStatManager._releaseLock(mob: Instance)
	if not mob then return end

	local mutex = _mutexTable[mob]
	if not mutex then return end

	-- Release the lock
	mutex.count -= 1

	-- Handle edge case: count should never be negative
	if mutex.count < 0 then
		warn("Lock count is negative. Possible bug in locking mechanism.")
		mutex.count = 0
	end

	-- Notify waiting threads
	mutex.signal:Fire()
end

function MobStatManager.new()
	if _instance == nil then
		_instance = setmetatable({}, MobStatManager)
		_instance._mobStats = {}
	end
	
	return _instance
end

function MobStatManager:isDead(mob: Instance): boolean
	if not mob or not mob:IsA("Instance") then
		warn("Invalid mob provided to registerMob")
		return
	end


	local isDead = false
	if _mobStats[mob] then
		isDead = _mobStats[mob].isDead
	end
	
	return isDead
end

--[[
    Registers a new mob with default or provided attributes.

    @param mob: Instance - The mob instance to register.
    @param params: table (optional) - Contains mob attributes:
        - maxHealth: number (default = 100)
        - health: number (default = 100)
        - attack: number (default = 10)
]]
function MobStatManager:registerMob(mob: Instance, params: MobParams?) : ()
	if not mob or not mob:IsA("Instance") then
		warn("Invalid mob provided to registerMob")
		return
	end
	
	MobStatManager._acquireLock(mob)

	-- Ensure params is always a valid table
	params = params or {} :: MobParams

	-- Populate _mobStats with provided or default values
	_mobStats[mob] = {
		isDead = false,
		maxHealth = params.maxHealth or DEFAULT_MOB_STATS.maxHealth,
		health = params.health or DEFAULT_MOB_STATS.health,
		attack = params.attack or DEFAULT_MOB_STATS.attack
	}

	-- Sync data to client using attributes
	mob:SetAttribute(ATTRIBUTES.MAX_HEALTH, _mobStats[mob].maxHealth)
	mob:SetAttribute(ATTRIBUTES.HEALTH, _mobStats[mob].health)
	
	-- states
	mob:SetAttribute(STATES.ISDEAD, _mobStats[mob].isDead)
	
	MobStatManager._releaseLock(mob)
end

--[[ 
    Unregisters a mob from the mob table and clears any attributes associated to the model
    
    @PARAM mob - mob to unregister
]]
function MobStatManager:unRegisterMob(mob: Instance)
	if not mob or not mob:IsA("Instance") then
		warn("Invalid mob provided to registerMob")
		return
	end

	MobStatManager._acquireLock(mob)
	_mobStats[mob] = nil

	-- Remove attributes
	mob:SetAttribute(ATTRIBUTES.MAX_HEALTH, nil) 
	mob:SetAttribute(ATTRIBUTES.HEALTH, nil)    

	MobStatManager._releaseLock(mob)
end


--[[
    Applies damage to a mob and updates its health.

    @param mob: Instance - The mob instance to damage.
    @param damage: number - The amount of damage to apply.
    @return boolean - True if damage was applied, false otherwise.
]]
function MobStatManager:takeDamage(mob: Instance, damage: number) : boolean
	if not mob or not damage then return false end
	
	MobStatManager._acquireLock(mob)
	
	if not _mobStats[mob] then 
		_releaseLock(mob)
		error("Error: E-012 Mob not registered on hit")
	end

	-- Apply damage
	local health = _mobStats[mob].health
	health -= damage
	local newHealthValue = math.max(health, 0)
	_mobStats[mob].health = newHealthValue

	-- Update health attribute (for client UI)
	mob:SetAttribute(ATTRIBUTES.HEALTH, newHealthValue)

	-- Handle mob death
	if newHealthValue <= 0 then
		_mobStats[mob].isDead = true
		mob:SetAttribute(STATES.ISDEAD, _mobStats[mob].isDead)
	end
	
	MobStatManager._releaseLock(mob)

	return true
end

-- Return the singleton instance
return MobStatManager