Repository: nvaccess/nvda

Issue:
NVDA takes the active profile as the default profile when Plugins are reloaded.
**Reported by Javichi on 2015-09-21 10:12**
I have a profile with a trigger. When it is active and I reload appModules and globalPlugins (with NVDA + Control + F3), profile seems to remain locked. While the application that triggered the profile is running, the profile list indicates that this is triggered, although the application is not in focus. When I close the application, in the profiles list no longer it appears as triggered, but the settings are still active. It seems as if NVDA thinks the profile was the default setting.
Perhaps this error is related to ticket # 3622



Discussion:
Comment 1 by jteh on 2015-09-21 21:31
Hmm. This is because the profile trigger is stored on the app module, but when it gets reloaded, that gets lost. It's similar to #2892.

I'm leaning towards dropping this Reload plugins functionality. It was only introduced for developers anyway, but if it's going to continue to cause problems, I'd rather it not be there at all.

Comment 2 by briang1 on 2015-09-22 11:26
Yes, often its better to restart nvda in any case to clean out any junk that might be a problem.
 As add ons and their local variables presumably are not stored together in some way, it would mean a complete rewrite of some code to get this to work in any case I assume.

I've never used reload plug ins indeed restart with plug ins dsabled is  of more use at times.

Comment 3 by Javichi on 2015-09-22 22:59
I know almost nothing NVDA code but I think that a possible solution would be to disable all profiles (triggered and manhuales, leaving only default settings) before to reload plugins.

I've taken a stab at fixing this in 0460156a. It almost works, but there's one remaining problem I haven't found a solution for yet. CC @leonardder.

Wouldn't it be possible to regenerate all nvda objects from scratch?

No. There's no registry of instantiated NVDAObjects. Furthermore, we 
have no idea what might be holding a reference to one of the (now 
broken) NVDAObjects.


Resolving patch:
diff --git a/source/appModuleHandler.py b/source/appModuleHandler.py
--- a/source/appModuleHandler.py
+++ b/source/appModuleHandler.py
@@ -178,9 +178,22 @@ def fetchAppModule(processID,appName):
 def reloadAppModules():
 	"""Reloads running appModules.
 	especially, it clears the cache of running appModules and deletes them from sys.modules.
-	Each appModule will be reloaded immediately as a reaction on a first event coming from the process.
+	Each appModule will then be reloaded immediately.
 	"""
 	global appModules
+	state = []
+	for mod in runningTable.itervalues():
+		state.append({key: getattr(mod, key) for key in ("processID",
+			# #2892: We must save nvdaHelperRemote handles, as we can't reinitialize without a foreground/focus event.
+			# Also, if there is an active context handle such as a loaded buffer,
+			# nvdaHelperRemote can't reinit until that handle dies.
+			"helperLocalBindingHandle", "_inprocRegistrationHandle",
+			# #5380: We must save config profile triggers so they can be cleaned up correctly.
+			# Otherwise, they'll remain active forever.
+			"_configProfileTrigger",
+		) if hasattr(mod, key)})
+		# #2892: Don't disconnect from nvdaHelperRemote during termination.
+		mod._helperPreventDisconnect = True
 	terminate()
 	del appModules
 	mods=[k for k,v in sys.modules.iteritems() if k.startswith("appModules") and v is not None]
@@ -188,6 +201,20 @@ def reloadAppModules():
 		del sys.modules[mod]
 	import appModules
 	initialize()
+	for entry in state:
+		pid = entry.pop("processID")
+		mod = getAppModuleFromProcessID(pid)
+		mod.__dict__.update(entry)
+	# The appModule property for existing NVDAObjects will now be None, since their AppModule died.
+	# Force focus, navigator, etc. objects to re-fetch,
+	# since NVDA depends on the appModule property for these.
+	for obj in itertools.chain((api.getFocusObject(), api.getNavigatorObject()), api.getFocusAncestors()):
+		try:
+			del obj._appModuleRef
+		except AttributeError:
+			continue
+		# Fetch and cache right away; the process could die any time.
+		obj.appModule
 
 def initialize():
 	"""Initializes the appModule subsystem. 
@@ -381,6 +408,8 @@ def terminate(self):
 		Subclasses should call the superclass method first.
 		"""
 		winKernel.closeHandle(self.processHandle)
+		if getattr(self, "_helperPreventDisconnect", False):
+			return
 		if self._inprocRegistrationHandle:
 			ctypes.windll.rpcrt4.RpcSsDestroyClientContext(ctypes.byref(self._inprocRegistrationHandle))
 		if self.helperLocalBindingHandle:


Test patch: