Mike Gorse (lightvortex) wrote,

caching in AT-SPI

I'm trying to decide if caching in AT-SPI2 should somehow work differently--and, if so, how.

Right now, at-spi2-atk maintains a hash table of "cached" objects. It listens for children-changed signals on objects in the cache. If a children-changed::add signal is fired for an object in the cache and the object does not have STATE_MANAGES_DESCENDANTS or STATE_DEFUNCT, then the new child also gets added to the cache, and an AddAccessible signal is sent over D-Bus (see below). At-spi2-atk holds a weak reference to the object and sends a RemoveAccessible signal when the object goes away. Objects can also be "leased" if they are not in the cache (if they are either descendants of objects with STATE_MANAGES_DESCENDANTS or objects for which the atk implementation never sent a children-changed::add), in which case a hard reference is held for 15 seconds.

At-spi2-atk exposes the "org.a11y.Atspi.Cache" interface over D-Bus. It has one method, GetItems, which retrieves a copy of at-spi2-atk's cache, and two signals, AddAccessible and RemoveAccessible. When libatspi initializes, it contacts at-spi2-registryd for a list of accessible applications and calls GetItems on each application, to initialize its cache. It listens for AddAccessible and RemoveAccessible signals and updates its cache accordingly. GetItems and AddAccessible send an object's name, description, parent, and children, which libatspi caches.

The QT AT-SPI bridge does not implement GetItems, nor does it send AddAccessible or RemoveAccessible.

If libatspi does not have cached information for an object from GetItems or AddAccessible, then it will lazily cache the object's name, role, description, and state set. Ie, if an AT asks for one of these, then it will make a D-Bus call, and then cache the result. Libatspi's cache keeps a hard reference to an object that it does not remove until RemoveAccessible is called or the application goes away. This means that libatspi will retain information about stale accessibles unless RemoveAccessible is sent, essentially causing a memory leak until the application goes away.

Frederik observed that he often sees several d-bus calls on one object caused by Orca asking for an object's name, then role, then state, for instance, and these could be combined into one call. Currently, name and role are properties, but state is not. I'm not sure there is a good reason for this. Iirc none of these were properties in the original code that Mark wrote, and I made name, description, and role into properties but not state, perhaps not for any good reason. The DBus specification has org.freedesktop.DBus.Properties.GetAll, which libatspi could use to cache all properties at once in the event that they were not originally cached. But I guess GetAll means "get all properties," not, for instance, "get all of the properties that aren't too expensive to calculate." Maybe the latter shouldn't be properties--that's the convention in C#, at least, to use a property for something that's trivial to return while using a GetSomethingOrOther() method for something that's more complex to retrieve, in order to convey that it's more complex to retrieve. But a piece of data like an object's state or child count might be simple for some toolkits to provide but more complex for others. Having STATE_MANAGES_DESCENANTS is supposed to be a suggestion that children shouldn't be enumerated. Fetching the number of children for a Gtk tree view used to involve counting rows and thus be expensive. Now it is trivial, according to Benjamin, with his refactor in Gtk 3.3. And CHildCount is currently a property... So fetching all properties might be expensive in that it'll cause things to be calculated that weren't needed in the first place, to the extent that calculating them would be expensive, while fetching one property at a time can be expensive in requiring several consecutive round-trip D-Bus calls.

Frederik seems to generally feel that there is too much caching in general, feels "forced" to send updates for some things because of caching, and probably isn't sending updates for a lot of state changes, meaning that there may well be issues using QT apps with Orca unless Orca has a QT script that disables caching.

I think we probably need to allow the toolkit to somehow tell AT-SPI what it intends and doesn't intend to update / what is safe to cache. DBus introspection can do this to some extent--it allows a property to have meta-markup saying whether or not it is guaranteed to emit a signal when its value changes. Perhaps we could utilize this. Now it relates to Benjamin talking about wanting the bridge to be a library that can talk to the toolkit.

Also, do we really even need org.a11y.Atspi.Cache? Would it suffice to have a weak-reference-based cache like what AT-SPI 1 had, perhaps coupled with use of GetAll, and/or cached properties invalidating after a certain (short) period of time? Perhaps this could be coupled with a method like GetItems but retrieving a sub-tree of objects that we're interested in--say, to handle the case of Orca creating a flat review context and wanting to inspect the accessibles representing the window. Anyway, the current design relies on the toolkit firing the appropriate events to update the cache (where AT-SPI1 did not to the same extent) or the AT to tell it not to cache certain types of things for the application, so it is kind of brittle and a bit all-or-nothing.

Some of this should probably go somewhere on live.gnome.org and/or into an overview document somewhere under at-spi2-core/doc. (Frederik was talking about wanting to write an overview document, like I should have done ages ago.)

Tags: accessibility
  • Post a new comment


    default userpic