mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-20 17:41:37 +00:00
fix(plugin): guard startSession against double-click re-entry
Closes #1046 (AI-assisted)
This commit is contained in:
parent
a30a8ecd0f
commit
914e5c5a83
3 changed files with 111 additions and 2 deletions
1
AI-canary.txt
Normal file
1
AI-canary.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This pull request is AI generated
|
||||||
|
|
@ -601,6 +601,13 @@ function App:useRunningConnectionInfo()
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:startSession()
|
function App:startSession()
|
||||||
|
if self.serveSession ~= nil then
|
||||||
|
-- A session is already active
|
||||||
|
-- Without this guard double clicking connect starts 2 serve sessions and leaks the first and crashes the plugn
|
||||||
|
Log.trace("Ignoring startSession because a session is already active")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local claimedLock, priorOwner = self:claimSyncLock()
|
local claimedLock, priorOwner = self:claimSyncLock()
|
||||||
if not claimedLock then
|
if not claimedLock then
|
||||||
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
|
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
|
||||||
|
|
@ -630,6 +637,8 @@ function App:startSession()
|
||||||
apiContext = apiContext,
|
apiContext = apiContext,
|
||||||
twoWaySync = Settings:get("twoWaySync"),
|
twoWaySync = Settings:get("twoWaySync"),
|
||||||
})
|
})
|
||||||
|
-- Assign before start() so the guard above rejects simultaneous calls
|
||||||
|
self.serveSession = serveSession
|
||||||
|
|
||||||
serveSession:setUpdateLoadingTextCallback(function(text: string)
|
serveSession:setUpdateLoadingTextCallback(function(text: string)
|
||||||
self:setState({
|
self:setState({
|
||||||
|
|
@ -825,8 +834,6 @@ function App:startSession()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
serveSession:start()
|
serveSession:start()
|
||||||
|
|
||||||
self.serveSession = serveSession
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:endSession()
|
function App:endSession()
|
||||||
|
|
|
||||||
101
plugin/src/App/init.spec.lua
Normal file
101
plugin/src/App/init.spec.lua
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
return function()
|
||||||
|
local App = require(script.Parent)
|
||||||
|
local ServeSession = require(script.Parent.Parent.ServeSession)
|
||||||
|
local ApiContext = require(script.Parent.Parent.ApiContext)
|
||||||
|
|
||||||
|
-- The module returns a wrapper function that renders the App component.
|
||||||
|
-- The App class itself isn't exported, so we pull it off the element it
|
||||||
|
-- creates in order to call its methods directly.
|
||||||
|
local AppComponent = App({}).component
|
||||||
|
|
||||||
|
-- A stubbed ServeSession that records nothing and does nothing, so that
|
||||||
|
-- startSession can run without touching the network.
|
||||||
|
local function makeServeSessionStub()
|
||||||
|
return {
|
||||||
|
setUpdateLoadingTextCallback = function() end,
|
||||||
|
hookPrecommit = function()
|
||||||
|
return function() end
|
||||||
|
end,
|
||||||
|
hookPostcommit = function()
|
||||||
|
return function() end
|
||||||
|
end,
|
||||||
|
onStatusChanged = function() end,
|
||||||
|
setConfirmCallback = function() end,
|
||||||
|
start = function() end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A minimal `self` with just the methods startSession touches, so we can
|
||||||
|
-- invoke App.startSession without mounting the whole component.
|
||||||
|
local function makeFakeSelf()
|
||||||
|
return {
|
||||||
|
serveSession = nil,
|
||||||
|
knownProjects = {},
|
||||||
|
-- claimSyncLock returns true in solo (no Team Create), which is the
|
||||||
|
-- "quick session" case the double-click bug happens in.
|
||||||
|
claimSyncLock = function()
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
getHostAndPort = function()
|
||||||
|
return "localhost", "34872"
|
||||||
|
end,
|
||||||
|
isAutoConnectPlaytestServerAvailable = function()
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
setState = function() end,
|
||||||
|
addNotification = function() end,
|
||||||
|
setPriorSyncInfo = function() end,
|
||||||
|
setRunningConnectionInfo = function() end,
|
||||||
|
clearRunningConnectionInfo = function() end,
|
||||||
|
releaseSyncLock = function() end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("startSession", function()
|
||||||
|
local realServeSessionNew = ServeSession.new
|
||||||
|
local realApiContextNew = ApiContext.new
|
||||||
|
local created
|
||||||
|
|
||||||
|
beforeEach(function()
|
||||||
|
created = 0
|
||||||
|
ServeSession.new = function()
|
||||||
|
created += 1
|
||||||
|
return makeServeSessionStub()
|
||||||
|
end
|
||||||
|
-- Stub ApiContext so construction stays offline and deterministic.
|
||||||
|
ApiContext.new = function()
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
afterEach(function()
|
||||||
|
ServeSession.new = realServeSessionNew
|
||||||
|
ApiContext.new = realApiContextNew
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("creates a session on the first call", function()
|
||||||
|
local self = makeFakeSelf()
|
||||||
|
AppComponent.startSession(self)
|
||||||
|
expect(created).to.equal(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("does not start a second session when called again while active", function()
|
||||||
|
local self = makeFakeSelf()
|
||||||
|
-- Two calls in a row mimic a double-click firing startSession twice
|
||||||
|
-- before the first session finishes. Without the re-entrancy guard
|
||||||
|
-- this creates two ServeSessions and leaks the first.
|
||||||
|
AppComponent.startSession(self)
|
||||||
|
AppComponent.startSession(self)
|
||||||
|
expect(created).to.equal(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("allows a new session after the previous one is cleared", function()
|
||||||
|
local self = makeFakeSelf()
|
||||||
|
AppComponent.startSession(self)
|
||||||
|
-- Disconnecting clears serveSession; a later connect should work.
|
||||||
|
self.serveSession = nil
|
||||||
|
AppComponent.startSession(self)
|
||||||
|
expect(created).to.equal(2)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue