Skip to content

Commit 2f2ad68

Browse files
committed
Fixes in device ownership changes
- When RequestConfigurationChange() is invoked before Device is attached to object hierarchy (i.e. doesn't have owner), apply changes in-place. This fixes a bug when calling AddStreamAsync() before calling AddDevice() would never create the stream. - Handle races between RequestConfigurationChange() and changing device owner from Plugin (in AddDevice and RemoveDevice). To avoid races, a new method Device::RequestOwnershipChange() is added, which is serialized with RequestConfigurationChange(). Plugin now uses that method to change device ownership. - Allow user to override all 4 methods: RequestConfigurationChange(), RequestOwnershipChange(), PerformConfigurationChange(), AbortConfigurationChange() Useful if the user want to replace handling of request changes entirely.
1 parent 90e8342 commit 2f2ad68

File tree

6 files changed

+111
-15
lines changed

6 files changed

+111
-15
lines changed

include/aspl/Device.hpp

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -921,14 +921,31 @@ class Device : public Object
921921
//! some time later HAL invokes PerformConfigurationChange(), which runs the
922922
//! function. HAL may also invoke AbortConfigurationChange() instead.
923923
//! @note
924-
//! If Context::Host is null, this method assumes that the plugin is not yet
925-
//! initialized and published to HAL, and in this case it just executes the
926-
//! function immediately.
927-
//! @note
928-
//! If invoked from PerformConfigurationChange(), assumes that we're already
929-
//! at the point where it's safe to change configuration and executes the
930-
//! function immediately.
931-
void RequestConfigurationChange(std::function<void()> func = {});
924+
//! There are three cases when this method decides that it's safe to invoke the
925+
//! function immediately ("in-place") instead of enqueueing it:
926+
//! - If Context::Host is null, the method assumes that the entire plugin is not
927+
//! yet initialized and published to HAL, and thus HAL doesn't use Device.
928+
//! - If Device::HasOwner() is false, the method assumes that the device itself
929+
//! is not added to the plugin hierarchy yet, and thus HAL doesn't use Device.
930+
//! - If the method is invoked from PerformConfigurationChange(), it assumes
931+
//! that we're already at the point where it's safe to change configuration.
932+
virtual void RequestConfigurationChange(std::function<void()> func = {});
933+
934+
//! Request device to change its owner.
935+
//! @remarks
936+
//! This method is called by Device owner (Plugin object) when device is added
937+
//! or removed to the plugin (and so becomes published or unpublished from HAL).
938+
//! This method implements thread-safe changing of the ownership and handles
939+
//! possible races with RequestConfigurationChange().
940+
//! It is needed because RequestConfigurationChange() mechanism is tightly
941+
//! coupled with the ownership state and should be taken into account when
942+
//! ownership changes.
943+
//! @note
944+
//! Normally @p owner is always a pointer to Plugin, and @p shouldHaveOwnership
945+
//! is true when adding device and false when removing.
946+
//! This method is expected to do all necessary housekeeping and invoke
947+
//! owner->AddOwnedObject(this) or owner->RemoveOwnedObject(this).
948+
virtual void RequestOwnershipChange(Object* owner, bool shouldHaveOwnership);
932949

933950
//! Called by the Host to allow the device to perform a configuration change
934951
//! that had been previously requested via a call to the Host method,
@@ -1246,7 +1263,7 @@ class Device : public Object
12461263
DoubleBuffer<std::variant<std::shared_ptr<IORequestHandler>, IORequestHandler*>>
12471264
ioHandler_;
12481265

1249-
std::unordered_map<UInt64, std::function<void()>> pendingConfigurationRequests_;
1266+
std::map<UInt64, std::function<void()>> pendingConfigurationRequests_;
12501267
UInt64 lastConfigurationRequestID_ = 0;
12511268
UInt64 insideConfigurationHandler_ = 0;
12521269

include/aspl/Object.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ class Object : public std::enable_shared_from_this<Object>
106106
//! Backs @c kAudioObjectPropertyOwner property.
107107
AudioObjectID GetOwnerID() const;
108108

109+
//! Check if the object is part of the hierarchy.
110+
//! Returns true if GetOwnerID() is not equal to kAudioObjectUnknown.
111+
//! @remarks
112+
//! By default, object does not have an owner. Until the object is
113+
//! attached to an owner, it is not part of the plugin object
114+
//! hierarchy and is not visible to HAL.
115+
bool HasOwner() const;
116+
109117
//! Get owned objects.
110118
//! Returns the list of objects to which this object is the owner.
111119
//! @remarks

include/aspl/Plugin.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class Plugin : public Object
164164
//! @}
165165

166166
private:
167-
mutable std::mutex writeMutex_;
167+
mutable std::recursive_mutex writeMutex_;
168168

169169
const PluginParameters params_;
170170

src/Device.cpp

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,7 +1592,11 @@ void Device::RequestConfigurationChange(std::function<void()> func)
15921592

15931593
auto host = GetContext()->Host.load();
15941594

1595-
if (host && !insideConfigurationHandler_) {
1595+
// Note: RequestConfigurationChange(), RequestOwnershipChange(), and
1596+
// PerformConfigurationChange() calls are serialized via a mutex.
1597+
// RequestOwnershipChange() changes HasOwner(), and PerformConfigurationChange()
1598+
// changes insideConfigurationHandler_.
1599+
if (host && HasOwner() && !insideConfigurationHandler_) {
15961600
const auto reqID = lastConfigurationRequestID_++;
15971601

15981602
GetContext()->Tracer->Message(
@@ -1610,6 +1614,68 @@ void Device::RequestConfigurationChange(std::function<void()> func)
16101614
}
16111615
}
16121616

1617+
void Device::RequestOwnershipChange(Object* owner, bool shouldHaveOwnership)
1618+
{
1619+
std::lock_guard writeLock(writeMutex_);
1620+
1621+
if (shouldHaveOwnership) {
1622+
if (HasOwner()) {
1623+
// Device owner is always either empty or Plugin object.
1624+
// If we already have an owner, we assume that it's
1625+
// already set to the same object and do nothing.
1626+
return;
1627+
}
1628+
1629+
GetContext()->Tracer->Message(
1630+
"Device::RequestOwnershipChange() attaching owner devID=%lu ownerID=%lu",
1631+
static_cast<unsigned long>(GetID()),
1632+
static_cast<unsigned long>(owner->GetID()));
1633+
1634+
// Device becomes visible to HAL.
1635+
// HasOwner() now will return true.
1636+
// RequestConfigurationChange() will enqueue changes instead of applying in-place.
1637+
owner->AddOwnedObject(shared_from_this());
1638+
} else {
1639+
if (!HasOwner()) {
1640+
// If we already don't have an owner, do nothing.
1641+
return;
1642+
}
1643+
1644+
GetContext()->Tracer->Message(
1645+
"Device::RequestOwnershipChange() detaching owner devID=%lu ownerID=%lu",
1646+
static_cast<unsigned long>(GetID()),
1647+
static_cast<unsigned long>(owner->GetID()));
1648+
1649+
// Device disappears HAL.
1650+
// HasOwner() now will return false.
1651+
// RequestConfigurationChange() will apply changes in-place.
1652+
owner->RemoveOwnedObject(GetID());
1653+
1654+
if (!pendingConfigurationRequests_.empty()) {
1655+
// If there are any enqueued changes, apply them now since it's not
1656+
// guaranteed now that HAL will ever handle them. But HAL *will* try
1657+
// to apply (some of) them, PerformConfigurationChange() will just
1658+
// ignore those requests because we've removed them from map here.
1659+
GetContext()->Tracer->Message(
1660+
"Device::RequestOwnershipChange()"
1661+
" applying pending configuration changes in-place"
1662+
" devID=%lu numChanges=%lu",
1663+
static_cast<unsigned long>(GetID()),
1664+
static_cast<unsigned long>(pendingConfigurationRequests_.size()));
1665+
1666+
// Iterate ordered map from lower to higher request IDs.
1667+
while (!pendingConfigurationRequests_.empty()) {
1668+
auto it = pendingConfigurationRequests_.begin();
1669+
1670+
const auto& func = it->second;
1671+
func();
1672+
1673+
pendingConfigurationRequests_.erase(it);
1674+
}
1675+
}
1676+
}
1677+
}
1678+
16131679
OSStatus Device::PerformConfigurationChange(AudioObjectID objectID,
16141680
UInt64 changeAction,
16151681
void* changeInfo)
@@ -1630,7 +1696,7 @@ OSStatus Device::PerformConfigurationChange(AudioObjectID objectID,
16301696
func();
16311697
} else {
16321698
GetContext()->Tracer->Message(
1633-
"Device::PerformConfigurationChange() request func is null reqID=%lu",
1699+
"Device::PerformConfigurationChange() ignoring null change request reqID=%lu",
16341700
static_cast<unsigned long>(reqID));
16351701
}
16361702

src/Object.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ AudioObjectID Object::GetOwnerID() const
7777
return ownerObjectID_;
7878
}
7979

80+
bool Object::HasOwner() const
81+
{
82+
return ownerObjectID_ != kAudioObjectUnknown;
83+
}
84+
8085
std::vector<AudioObjectID> Object::GetOwnedObjectIDs(AudioObjectPropertyScope scope,
8186
AudioClassID classID) const
8287
{

src/Plugin.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ void Plugin::AddDevice(std::shared_ptr<Device> device)
8888

8989
GetContext()->Tracer->OperationBegin(op);
9090

91-
AddOwnedObject(device);
92-
9391
{
9492
auto devices = devices_.Get();
9593

@@ -114,6 +112,8 @@ void Plugin::AddDevice(std::shared_ptr<Device> device)
114112
deviceByUID_.Set(std::move(deviceByUID));
115113
}
116114

115+
device->RequestOwnershipChange(this, true);
116+
117117
NotifyPropertiesChanged(
118118
{kAudioObjectPropertyOwnedObjects, kAudioPlugInPropertyDeviceList});
119119

@@ -158,7 +158,7 @@ void Plugin::RemoveDevice(std::shared_ptr<Device> device)
158158
deviceByUID_.Set(std::move(deviceByUID));
159159
}
160160

161-
RemoveOwnedObject(device->GetID());
161+
device->RequestOwnershipChange(this, false);
162162

163163
NotifyPropertiesChanged(
164164
{kAudioObjectPropertyOwnedObjects, kAudioPlugInPropertyDeviceList});

0 commit comments

Comments
 (0)