Skip to content

Decouple BgThread from braille module#14312

Merged
feerrenrut merged 17 commits into
nvaccess:masterfrom
LeonarddeR:bgThreadRewrite
Nov 22, 2022
Merged

Decouple BgThread from braille module#14312
feerrenrut merged 17 commits into
nvaccess:masterfrom
LeonarddeR:bgThreadRewrite

Conversation

@LeonarddeR

@LeonarddeR LeonarddeR commented Oct 29, 2022

Copy link
Copy Markdown
Collaborator

Link to issue number:

Closes #14130
Slightly related to #14147

Summary of the issue:

In #14130, I raised the concern that the background thread for i/o is too tightly coupled too the braille module. This required the hwIo module to queue an APC to the braille background thread.

Description of user facing changes

None, apart from more safety checks that might impose stability improvements.

Description of development approach

  1. Moved the background thread from the braille module. Instead make it a class on the hwIo module. It is also no longer a singleton and therefore add-ons can create their own i/o thread. I'm intending to use this in a new add-on.
  2. Rather than all cases where APCs have to be queued to the background thread being responsible for wrapping themselves in a winKernel.PAPCFUNC, the queue function now takes a callable or lambda. The function is converted to an APC when queued. There is a cache on the thread class to keep references to wrapped APCs until they are finished. The wrapper also ensures that the APC doesn't execute when the thread is about to exit.
  3. hwIo.base.IoBase now contains a new _initialRead method that initiates reading on the background thread. This method can be overridden on subclasses to outsource the reading to another thread.

Point 1 and 3 above together solve my concerns as noted in #14130

Testing strategy:

We need to thoroughly test braille display i/o for these changes.
I ran the pr build for several days without noticeable impact, in fact I was under the impression that braille initialization time decreased slightly with this approach.

Known issues with pull request:

  • This removes braille._BgThread and can be considered API breaking, however given the _BgThread class was underscore prefixed, it can be considered private code. This should be safe for 2023.1.

Change log entries:

For Developers

  • The singleton braille._BgThread class that provides background i/o for threadsafe braille display drivers has been moved to its own module in hwIo.ioThread. It is no longer a singleton. The single instance in NVDA core can be accessed using hwIo.bgThread, however add-on authors are encouraged to use their own instance when doing hardware i/o. (hwIo.base.IoBase relies on braille._BgThread #14130)

Code Review Checklist:

  • Pull Request description:
    • description is up to date
    • change log entries
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • API is compatible with existing add-ons.
  • Documentation:
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • Security precautions taken.

@LeonarddeR LeonarddeR requested a review from a team as a code owner October 29, 2022 13:34
@LeonarddeR LeonarddeR requested a review from seanbudd October 29, 2022 13:34
@AppVeyorBot

This comment was marked as outdated.

@AppVeyorBot

This comment was marked as outdated.

Comment thread source/braille.py Outdated
Comment thread source/braille.py Outdated
Comment thread source/braille.py Outdated
Comment thread source/braille.py Outdated
Comment thread tests/system/libraries/SystemTestSpy/speechSpyGlobalPlugin.py Outdated
Comment thread source/hwIo/ioThread.py Outdated
Comment thread source/hwIo/ioThread.py Outdated
Comment thread source/hwIo/ioThread.py
@seanbudd seanbudd requested a review from feerrenrut November 2, 2022 03:23
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
@AppVeyorBot

This comment was marked as outdated.

…, the display might send an ack after closing the handle, resulting in an error
@seanbudd seanbudd marked this pull request as draft November 10, 2022 01:24
@seanbudd

Copy link
Copy Markdown
Member

Converting to draft due to merge conflicts.

@LeonarddeR

Copy link
Copy Markdown
Collaborator Author

Solved merge conflicts and confirmed that stuff still works as expected.

@LeonarddeR LeonarddeR marked this pull request as ready for review November 10, 2022 15:24

@feerrenrut feerrenrut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to need to go over this again. But there are a few things I picked up this time.



def _crashNVDA():
def _crashNVDA(param: int = 0):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a docstring for this param? Or perhaps use a lambda or wrap the call to this to adapt it to the queueAsApc requirements.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread source/hwIo/ioThread.py
Comment thread source/hwIo/ioThread.py Outdated
Comment on lines +38 to +39
if self.thread:
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clearer if this class inherited from threading.Thread and the lifecycle of IoThread was managed by some owner.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good idea. I applied that suggestion.

Comment thread source/braille.py Outdated
_BgThread.ackTimerHandle,
int(handler.display.timeout*2000),
self.ackTimerHandle,
int(self.display.timeout * 2000),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know about this value? Why is it multiplied by 2000?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is converted to milli seconds and then multiplied by two. I added a descriptive comment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be clearer if the two intentions were separated (IE, twice time out, convert to MS). Using named constants is a step better than a comment because the name is more likely to be updated to stay correct than a nearby comment.

				SECOND_TO_MS = 1000
				winKernel.setWaitableTimer(
					self.ackTimerHandle,
					# Wait twice the display driver timeout for acknowledgement packets
					# Note: timeout is in seconds whereas setWaitableTimer expects milliseconds
					int(self.display.timeout * 2 * SECOND_TO_MS ),

@AppVeyorBot

Copy link
Copy Markdown

See test results for failed build of commit 2fefdf0582

@seanbudd seanbudd added the merge-early Merge Early in a developer cycle label Nov 17, 2022
Comment thread source/hwIo/ioThread.py Outdated
Comment thread source/braille.py Outdated
_BgThread.ackTimerHandle,
int(handler.display.timeout*2000),
self.ackTimerHandle,
int(self.display.timeout * 2000),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be clearer if the two intentions were separated (IE, twice time out, convert to MS). Using named constants is a step better than a comment because the name is more likely to be updated to stay correct than a nearby comment.

				SECOND_TO_MS = 1000
				winKernel.setWaitableTimer(
					self.ackTimerHandle,
					# Wait twice the display driver timeout for acknowledgement packets
					# Note: timeout is in seconds whereas setWaitableTimer expects milliseconds
					int(self.display.timeout * 2 * SECOND_TO_MS ),

@AppVeyorBot

This comment was marked as outdated.

@feerrenrut

Copy link
Copy Markdown
Contributor

@LeonarddeR I've re-worded the developer change log entry, could you confirm the following is accurate:

- The singleton ``braille._BgThread`` class has been replaced with ``hwIo.ioThread.IoThread``. (#14130)
  - A single instance ``hwIo.bgThread`` (in NVDA core) of this class provides background i/o for thread safe braille display drivers.
  - This new class is not a singleton by design, add-on authors are encouraged to use their own instance when doing hardware i/o.
  -
-

@LeonarddeR

LeonarddeR commented Nov 21, 2022 via email

Copy link
Copy Markdown
Collaborator Author

@feerrenrut feerrenrut merged commit 9db8475 into nvaccess:master Nov 22, 2022
@nvaccessAuto nvaccessAuto added this to the 2023.1 milestone Nov 22, 2022
burmancomp added a commit to burmancomp/nvda that referenced this pull request Nov 23, 2022
seanbudd pushed a commit that referenced this pull request Mar 30, 2023
…s are safe (#14627)

Follow up of #14312

Summary of the issue:
In #14312, we decoupled the background I/O thread from the braille module. However, the thread was still bound to IoBase in it's _initialRead method unless you'd override it.
While I hoped the changes in #14312 would decrease crashing of braille, this was indeed the cause. However, crashes are still occurring here and there. I'm pretty sure I know the cause lies in the IO done completion routines that are still executed on the IO thread without ensuring that the instances of the routines still exist. I've seen this causing several access violation errors.
Furthermore, #14312 saved bound methods in a dictionary on the iOThread. While this shouldn't be a big problem, this could cause potential issues with garbage collection (i.e. instances being kept alife forever because the APC of an instance was never called and it would be stuck in the cached apc dictionary).

Description of user facing changes
Hopefully, more stability.

Description of development approach
Added the ioThread as a constructor method to IoBase and derivatives as an optional argument. If not provided (default), the default IoThread is used. It will be added as a weak reference on the IoBase instance. Thereby it is no longer necessary to subclass IoBase or a derivative to override _initialRead to use another thread.
Added IoThread.setWaitableTimer and IoThread.getCompletionRoutine, also ensuring that the function pointers are available when the background thread tries to call them. The function wrapper keeps a weak reference to the method it wraps. Thereby it ensures that a method of a died instance is no longer executed.
IoThread.queueAsApc still stores a strong reference to the wrapped method on its APC, to keep backwards compatibility. This will be changed to weak references starting in NVDA 2024.1. API deprecation logic is in place.
Removed the pre_IoThreadStop extension point. It was added in 2023.1 but never implemented and announced, so I think it is safe to do so.
seanbudd pushed a commit that referenced this pull request Jun 2, 2023
Related to #14899, #14312, #14627
Fixes #14895

Summary of the issue:
Despite several attempts to fix this, NVDA's IoThread can crash without a clear cause.

Description of user facing changes
Less crashes, most likely, as tests indicate that this is the case.

Description of development approach
As proposed by @jcsteh , rather than creating a new function pointer for every APC or completion routine call, use a single internal APC and completion routine and use an internal cache to store the python functions, not the actual APC functions.
@LeonarddeR LeonarddeR deleted the bgThreadRewrite branch August 23, 2025 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-early Merge Early in a developer cycle

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hwIo.base.IoBase relies on braille._BgThread

5 participants