libgdx / libgdx/libgdx

[Bug] Deadlock w/ `Timer` and `Lwjgl3Application`

Open
#7,548 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

LWJGL3
Dominant language
Java
Stars
25.4k
Forks
6.5k
Avg merge
1d 16h
Merged PRs (30d)
5

Description

Issue details

The situation is as follows:

  1. Create a Lwjgl3Application, and from that application:
  2. Kick off a different thread that creates a Timer, which causes the timer instance to be set and the gdx files instance to be cached.
  3. Swap instances to a different Lwjgl3Application. (Timer implies it supports this by checking if it's local version of files matches that of the static Gdx.files, which will be important later).

Now, the deadlock can happen like so:

  1. [Main Thread] Lwjgl3Application.java, loop() is called, which is synchronized on lifecycleListeners. This calls render().
  2. [Thread 2] This thread now tries to create a new timer (for some application-logic-specific reason). This thread synchronizes on threadLock in the instance() call, and so that lock is now held. When it calls the static thread() method, because GDX files has changed, it calls dispose(). While in dispose(), it tries to call removeLifecycleListener on Lwjgl3Application. We need the lifecycleListeners lock to do so, but that is held by Main Thread. So we wait.
  3. [Main Thread] Within that render() call, something tries to create a Timer. We are stuck waiting for the threadLock lock, which is held by Thread 2.

Now, each thread is waiting for a lock that the other holds, and is deadlocked.
I was able to get this deadlock to happen in the intellij debugger and used a thread dump to confirm all of this.

Version of libGDX and/or relevant dependencies

1.13.0

Stacktrace

Thread 2:

"Test worker@1" tid=0x1 nid=NA waiting for monitor entry
  java.lang.Thread.State: BLOCKED
	 blocks Timer@26630
	 blocks Timer@26863
	 blocks pool-52-thread-2@26876
	 blocks Timer@27042
	 blocks Timer@27372
	 waiting for pool-52-thread-2@26876 to release lock on <0x6b93> (a java.lang.Object)
	  at com.badlogic.gdx.utils.Timer.instance(Timer.java:38)
	  at com.badlogic.gdx.utils.Timer.schedule(Timer.java:187)

Thread 1:

"pool-52-thread-2@26876" tid=0x93 nid=NA waiting for monitor entry
  java.lang.Thread.State: BLOCKED
	 blocks Test worker@1
	 waiting for Test worker@1 to release lock on <0x7a0e> (a com.badlogic.gdx.utils.Array)
	  at com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application.removeLifecycleListener(Lwjgl3Application.java:402)
	  at com.badlogic.gdx.utils.Timer$TimerThread.dispose(Timer.java:367)
	  at com.badlogic.gdx.utils.Timer.thread(Timer.java:48)
	  - locked <0x6b93> (a java.lang.Object)
	  at com.badlogic.gdx.utils.Timer.instance(Timer.java:39)
	  at com.badlogic.gdx.utils.Timer.schedule(Timer.java:187)
Please select the affected platforms
  • Android
  • iOS
  • HTML/GWT
  • Windows
  • Linux
  • macOS

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading com.badlogic.gdx.utils.Timer.java, especially instance(), thread(), and TimerThread.dispose(), alongside Lwjgl3Application.java loop() and removeLifecycleListener(). Reproduce the described application-switch sequence with the thread dump to confirm the lock cycle. Done means the sequence no longer deadlocks on Windows, Linux, or macOS.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
desktop-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.