Thanks Stephan for taking the time to read and reply.
I've read your answer again and again and am still wrapping my head around what "stuttering" means and how to debug it.
I've been using the VSCode extension to write and test the specification... and got different results.
This is the specification I ended up with:
---- MODULE loop ----
EXTENDS TLC, Integers, FiniteSets
VARIABLES worker_status, work_done, loop_done
Parallelism == 2
Workers == {"w1", "w2", "w3"}
WorkersCount == Cardinality(Workers)
Processes == {"loop"} \cup Workers
RunningCount == IF \E x \in Workers: worker_status[x] = "running"
THEN Cardinality({x \in Workers: worker_status[x] = "running"})
ELSE 0
ExitedCount == IF \E x \in Workers: worker_status[x] = "exited"
THEN Cardinality({x \in Workers: worker_status[x] = "exited"})
ELSE 0
vars == << worker_status, work_done, loop_done >>
Init ==
/\ worker_status = [x \in Workers |-> "not_started"]
/\ work_done = 0
/\ loop_done = FALSE
L_Start_Worker ==
/\ RunningCount < Parallelism
/\ \E worker \in Workers:
/\ worker_status[worker] = "not_started"
/\ worker_status' = [worker_status EXCEPT ![worker] = "running"]
/\ UNCHANGED << work_done, loop_done >>
L_Complete ==
/\ ExitedCount = WorkersCount
/\ loop_done' = TRUE
/\ UNCHANGED << worker_status, work_done >>
W_Work(self) ==
/\ worker_status[self] = "running"
/\ work_done' = work_done + 1
/\ worker_status' = [worker_status EXCEPT ![self] = "exiting"]
/\ UNCHANGED << loop_done >>
W_Exit(self) ==
/\ worker_status[self] = "exiting"
/\ worker_status' = [worker_status EXCEPT ![self] = "exited"]
/\ UNCHANGED << work_done, loop_done >>
loop == L_Start_Worker \/ L_Complete
worker(self) == W_Work(self) \/ W_Exit(self)
Next == loop
\/ (\E self \in Workers: worker(self))
\/ (loop_done = TRUE /\ UNCHANGED vars)
Spec == /\ Init /\ [][Next]_vars
/\ WF_vars(loop)
/\ \A self \in Workers: WF_vars(worker(self))
ParallelismRespected == RunningCount <= Parallelism
AllWorkDone == <>[](work_done = WorkersCount)
====
I've simplified it a bit and removed what redundancy there was.
Running the above specification with the ParallelismRespected invariant and AllWorkDone property does not fail in the TLA+ toolbox, but keeps failing with stuttering in VSCode with the following .cfg file:
```
INIT Init
NEXT Next
INVARIANT ParallelismRespected
PROPERTY AllWorkDone
```
I might be doing something different for the two environments not to match ... I will check more.
I'm posting this for my future reference and in hopes it could help someone else.