Skip to content

Commit 871ce58

Browse files
jloypixar-oss
authored andcommitted
exec, execUsd: request expiration
A request is built from a collection of value keys. Each of these value keys contains a provider, which is a scene description object that provides the relevant scope for computation definitions and values. These scene objects can become invalid, for example, by deleting them from the underlying stage. When a provider becomes invalid, it is no longer meaningful to compute the value that was previously specified by that value key. When a scene object becomes invalid, we "expire" the relevant request index and invoke invalidation callbacks for that index a final time. Once an index has been expired, the whole request may no longer be prepared or computed. However, the request will continue to receive invalidation for unexpired indices. ExecSystem::InvalidateAll expires all indices across all requests. In addition to the expiration described above, it clears most internal request impl data structures. Expiration does not delete the request impl object itself because the impl is exclusively owned by ExecUsdRequest. (Internal change: 2371430)
1 parent b58540c commit 871ce58

18 files changed

+829
-33
lines changed

pxr/exec/exec/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pxr_library(exec
2929
builtinComputations
3030
cacheView
3131
computationBuilders
32+
debugCodes
3233
requestImpl
3334
system
3435
systemChangeProcessor

pxr/exec/exec/debugCodes.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// Copyright 2025 Pixar
3+
//
4+
// Licensed under the terms set forth in the LICENSE.txt file available at
5+
// https://openusd.org/license.
6+
//
7+
#include "pxr/exec/exec/debugCodes.h"
8+
9+
#include "pxr/base/tf/registryManager.h"
10+
11+
PXR_NAMESPACE_OPEN_SCOPE
12+
13+
TF_REGISTRY_FUNCTION(TfDebug)
14+
{
15+
TF_DEBUG_ENVIRONMENT_SYMBOL(EXEC_REQUEST_EXPIRATION,
16+
"Report when request indices are expired.");
17+
18+
TF_DEBUG_ENVIRONMENT_SYMBOL(EXEC_REQUEST_INVALIDATION,
19+
"Report when requests are notified of invalidation");
20+
}
21+
22+
PXR_NAMESPACE_CLOSE_SCOPE

pxr/exec/exec/debugCodes.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Copyright 2025 Pixar
3+
//
4+
// Licensed under the terms set forth in the LICENSE.txt file available at
5+
// https://openusd.org/license.
6+
//
7+
#ifndef PXR_EXEC_EXEC_DEBUG_CODES_H
8+
#define PXR_EXEC_EXEC_DEBUG_CODES_H
9+
10+
#include "pxr/pxr.h"
11+
12+
#include "pxr/base/tf/debug.h"
13+
14+
PXR_NAMESPACE_OPEN_SCOPE
15+
16+
TF_DEBUG_CODES(
17+
EXEC_REQUEST_EXPIRATION,
18+
EXEC_REQUEST_INVALIDATION
19+
);
20+
21+
PXR_NAMESPACE_CLOSE_SCOPE
22+
23+
#endif

pxr/exec/exec/requestImpl.cpp

Lines changed: 160 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "pxr/exec/exec/authoredValueInvalidationResult.h"
1010
#include "pxr/exec/exec/cacheView.h"
11+
#include "pxr/exec/exec/debugCodes.h"
1112
#include "pxr/exec/exec/definitionRegistry.h"
1213
#include "pxr/exec/exec/disconnectedInputsInvalidationResult.h"
1314
#include "pxr/exec/exec/program.h"
@@ -33,6 +34,8 @@
3334
#include "pxr/exec/vdf/scheduler.h"
3435
#include "pxr/exec/vdf/types.h"
3536

37+
#include <string_view>
38+
3639
PXR_NAMESPACE_OPEN_SCOPE
3740

3841
Exec_RequestImpl::Exec_RequestImpl(
@@ -56,11 +59,46 @@ Exec_RequestImpl::~Exec_RequestImpl()
5659
}
5760
}
5861

62+
static void
63+
_OutputInvalidationResultDebugMsg(
64+
const std::string_view label,
65+
const ExecRequestIndexSet &indices)
66+
{
67+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(
68+
"[%.*s]\n",
69+
static_cast<int>(label.size()), label.data());
70+
71+
std::vector<int> sortedIndices(indices.begin(), indices.end());
72+
std::sort(sortedIndices.begin(), sortedIndices.end());
73+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(" indices:");
74+
for (const int index : sortedIndices) {
75+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(" %d", index);
76+
}
77+
}
78+
79+
static void
80+
_OutputInvalidationResultDebugMsg(
81+
const std::string_view label,
82+
const ExecRequestIndexSet &indices,
83+
const EfTimeInterval &interval)
84+
{
85+
_OutputInvalidationResultDebugMsg(label, indices);
86+
87+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(
88+
"\n interval: %s\n",
89+
interval.GetAsString().c_str());
90+
}
91+
5992
void
6093
Exec_RequestImpl::DidInvalidateComputedValues(
6194
const Exec_AuthoredValueInvalidationResult &invalidationResult)
6295
{
6396
if (!_valueCallback || _leafOutputs.empty()) {
97+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(
98+
"[%s] %s\n", TF_FUNC_NAME().c_str(),
99+
!_valueCallback
100+
? "No value invalidation callback"
101+
: "Request has not been prepared");
64102
return;
65103
}
66104

@@ -91,6 +129,10 @@ Exec_RequestImpl::DidInvalidateComputedValues(
91129
// Only invoke the invalidation callback if there are any invalid indices
92130
// from this request.
93131
if (!invalidIndices.empty()) {
132+
if (ARCH_UNLIKELY(TfDebug::IsEnabled(EXEC_REQUEST_INVALIDATION))) {
133+
_OutputInvalidationResultDebugMsg(
134+
TF_FUNC_NAME(), invalidIndices, invalidInterval);
135+
}
94136
TRACE_FUNCTION_SCOPE("value invalidation callback");
95137
_valueCallback(invalidIndices, invalidInterval);
96138
}
@@ -101,6 +143,11 @@ Exec_RequestImpl::DidInvalidateComputedValues(
101143
const Exec_DisconnectedInputsInvalidationResult &invalidationResult)
102144
{
103145
if (!_valueCallback || _leafOutputs.empty()) {
146+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(
147+
"[%s] %s\n", TF_FUNC_NAME().c_str(),
148+
!_valueCallback
149+
? "No value invalidation callback"
150+
: "Request has not been prepared");
104151
return;
105152
}
106153

@@ -130,6 +177,10 @@ Exec_RequestImpl::DidInvalidateComputedValues(
130177
// Only invoke the invalidation callback if there are any invalid indices
131178
// from this request.
132179
if (!invalidIndices.empty()) {
180+
if (ARCH_UNLIKELY(TfDebug::IsEnabled(EXEC_REQUEST_INVALIDATION))) {
181+
_OutputInvalidationResultDebugMsg(
182+
TF_FUNC_NAME(), invalidIndices, invalidInterval);
183+
}
133184
TRACE_FUNCTION_SCOPE("value invalidation callback");
134185
_valueCallback(invalidIndices, invalidInterval);
135186
}
@@ -140,6 +191,11 @@ Exec_RequestImpl::DidChangeTime(
140191
const Exec_TimeChangeInvalidationResult &invalidationResult)
141192
{
142193
if (!_timeCallback || _leafOutputs.empty()) {
194+
TF_DEBUG(EXEC_REQUEST_INVALIDATION).Msg(
195+
"[%s] %s\n", TF_FUNC_NAME().c_str(),
196+
!_valueCallback
197+
? "No time invalidation callback"
198+
: "Request has not been prepared");
143199
return;
144200
}
145201

@@ -168,11 +224,48 @@ Exec_RequestImpl::DidChangeTime(
168224
// Only invoke the invalidation callback if there are any invalid indices
169225
// from this request.
170226
if (!invalidIndices.empty()) {
227+
if (ARCH_UNLIKELY(TfDebug::IsEnabled(EXEC_REQUEST_INVALIDATION))) {
228+
_OutputInvalidationResultDebugMsg(
229+
TF_FUNC_NAME(), invalidIndices);
230+
}
171231
TRACE_FUNCTION_SCOPE("time change callback");
172232
_timeCallback(invalidIndices);
173233
}
174234
}
175235

236+
void
237+
Exec_RequestImpl::Expire()
238+
{
239+
TF_DEBUG(EXEC_REQUEST_EXPIRATION)
240+
.Msg("[%s] Expiring request %p\n", TF_FUNC_NAME().c_str(), this);
241+
242+
if (!TF_VERIFY(_system, "Attempted to expire an expired request")) {
243+
return;
244+
}
245+
246+
TRACE_FUNCTION();
247+
248+
// If the request has never been prepared (or just contains no values to
249+
// compute) there's no need push index-specific expiration.
250+
if (!_leafOutputs.empty()) {
251+
TRACE_FUNCTION_SCOPE("Expiring all indices");
252+
253+
ExecRequestIndexSet allIndices;
254+
const size_t numLeafOutputs = _leafOutputs.size();
255+
allIndices.reserve(numLeafOutputs);
256+
for (size_t i=0; i<numLeafOutputs; ++i) {
257+
allIndices.insert(i);
258+
}
259+
_ExpireIndices(allIndices);
260+
}
261+
262+
// Because we're expiring the whole request, we do more than just expiring
263+
// all the indices. It is guaranteed that no more invalidation can occur
264+
// so the request removes itself from the system's tracker and drops all
265+
// its data structures.
266+
_Discard();
267+
}
268+
176269
// Returns a value extractor suitable for the given value key according to its
177270
// computation definition.
178271
//
@@ -271,7 +364,9 @@ Exec_RequestImpl::_Compile(
271364
_computeRequest.reset();
272365
_schedule.reset();
273366
_lastInvalidatedIndices.Resize(_leafOutputs.size());
274-
_lastInvalidatedIndices.ClearAll();
367+
// These bits are set instead of cleared because clients are notified of
368+
// invalidation only after computing values.
369+
_lastInvalidatedIndices.SetAll();
275370

276371
// We must greedily build the leaf node to index map. When requests are
277372
// informed of network edits, some leaf nodes may have already been
@@ -284,6 +379,12 @@ Exec_RequestImpl::_Schedule()
284379
{
285380
TfAutoMallocTag tag("Exec", __ARCH_PRETTY_FUNCTION__);
286381

382+
// If there's nothing to compute, there's nothing to schedule.
383+
if (_leafOutputs.empty()) {
384+
_schedule.reset();
385+
return;
386+
}
387+
287388
// The compute request only needs to be rebuilt if the compiled outputs
288389
// change.
289390
if (!_computeRequest) {
@@ -312,7 +413,7 @@ Exec_RequestImpl::_Compute()
312413
{
313414
TfAutoMallocTag tag("Exec", __ARCH_PRETTY_FUNCTION__);
314415

315-
if (!TF_VERIFY(_system)) {
416+
if (!TF_VERIFY(_system) || !_schedule) {
316417
return Exec_CacheView();
317418
}
318419

@@ -338,6 +439,60 @@ Exec_RequestImpl::_RequiresCompilation() const
338439
|| (TF_VERIFY(_system) && _system->_HasPendingRecompilation());
339440
}
340441

442+
void
443+
Exec_RequestImpl::_ExpireIndices(const ExecRequestIndexSet &expired)
444+
{
445+
// If the request has never been prepared, there's no invalidation to push
446+
// to clients.
447+
if (_leafOutputs.empty()) {
448+
return;
449+
}
450+
451+
TRACE_FUNCTION();
452+
453+
TF_DEBUG(EXEC_REQUEST_EXPIRATION)
454+
.Msg("[%s] Expiring %zu indices\n",
455+
TF_FUNC_NAME().c_str(), expired.size());
456+
457+
const bool isNewlyInvalidInterval =
458+
!_lastInvalidatedInterval.IsFullInterval();
459+
ExecRequestIndexSet newlyInvalidIndices;
460+
newlyInvalidIndices.reserve(expired.size());
461+
for (const int idx : expired) {
462+
if (isNewlyInvalidInterval || !_lastInvalidatedIndices.IsSet(idx)) {
463+
newlyInvalidIndices.insert(idx);
464+
_lastInvalidatedIndices.Set(idx);
465+
}
466+
_leafOutputs[idx] = VdfMaskedOutput();
467+
_extractors[idx] = Exec_ValueExtractor();
468+
}
469+
_schedule.reset();
470+
_computeRequest.reset();
471+
472+
if (_valueCallback && !newlyInvalidIndices.empty()) {
473+
if (ARCH_UNLIKELY(TfDebug::IsEnabled(EXEC_REQUEST_INVALIDATION))) {
474+
_OutputInvalidationResultDebugMsg(
475+
TF_FUNC_NAME(), expired, EfTimeInterval::GetFullInterval());
476+
}
477+
TRACE_FUNCTION_SCOPE("value invalidation callback");
478+
_valueCallback(expired, EfTimeInterval::GetFullInterval());
479+
}
480+
481+
_lastInvalidatedInterval = EfTimeInterval::GetFullInterval();
482+
}
483+
484+
void
485+
Exec_RequestImpl::_Discard()
486+
{
487+
_system->_requestTracker->Remove(this);
488+
_system = nullptr;
489+
TfReset(_leafOutputs);
490+
TfReset(_extractors);
491+
TfReset(_leafNodeToIndex);
492+
_valueCallback = nullptr;
493+
_timeCallback = nullptr;
494+
}
495+
341496
void
342497
Exec_RequestImpl::_BuildLeafNodeToIndexMap()
343498
{
@@ -355,6 +510,9 @@ Exec_RequestImpl::_BuildLeafNodeToIndexMap()
355510
_leafNodeToIndex.reserve(_leafOutputs.size());
356511
for (size_t i = 0; i < _leafOutputs.size(); ++i) {
357512
const VdfMaskedOutput &sourceOutput = _leafOutputs[i];
513+
if (!sourceOutput) {
514+
continue;
515+
}
358516
for (const VdfConnection *const connection :
359517
sourceOutput.GetOutput()->GetConnections()) {
360518
const VdfNode &targetNode = connection->GetTargetNode();

pxr/exec/exec/requestImpl.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ class Exec_RequestImpl
6161
void DidChangeTime(
6262
const Exec_TimeChangeInvalidationResult &invalidationResult);
6363

64+
/// Expires all request indices and discards the request.
65+
///
66+
/// Sends value invalidation for all indicies over all time and renders
67+
/// the request unusuable for any future operation.
68+
///
69+
void Expire();
70+
6471
protected:
6572
EXEC_API
6673
Exec_RequestImpl(
@@ -94,15 +101,30 @@ class Exec_RequestImpl
94101
EXEC_API
95102
bool _RequiresCompilation() const;
96103

104+
/// Expires the indices in \p expired.
105+
///
106+
/// Invalidation callbacks will be invoked for these indices one final
107+
/// time. No values will be extractable and no further invalidation will
108+
/// be sent for these indices.
109+
///
110+
EXEC_API
111+
void _ExpireIndices(const ExecRequestIndexSet &expired);
112+
113+
/// Removes the request from the system.
114+
///
115+
/// This prevents any further notification and releases internal request
116+
/// data structures.
117+
///
118+
EXEC_API
119+
void _Discard();
120+
97121
private:
98122
// Ensures the _leafNodeToIndex map is up-to-date.
99-
EXEC_API
100123
void _BuildLeafNodeToIndexMap();
101124

102125
// Turns invalid leaf nodes into a set of requested - and not previously
103126
// invalidated - indices.
104127
//
105-
EXEC_API
106128
void _InvalidateLeafOutputs(
107129
bool isNewlyInvalidInterval,
108130
TfSpan<const VdfNode *const> leafNodes,

0 commit comments

Comments
 (0)