ppy/osu · error · InvalidOperationException

A {nameof(PoolableSkinnableSample)} cannot be applied multip

Error message

A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s.

What it means

Thrown by PoolableSkinnableSample.Apply(ISampleInfo) when sampleInfo is already non-null, enforcing that a pooled sample is single-use: exactly one ISampleInfo may ever be applied during its lifetime. Re-applying would leave stale volume/sample state.

Source

Thrown at osu.Game/Skinning/PoolableSkinnableSample.cs:62

        /// Creates a new <see cref="PoolableSkinnableSample"/> with an applied <see cref="ISampleInfo"/>.
        /// </summary>
        /// <param name="sampleInfo">The <see cref="ISampleInfo"/> to attach.</param>
        public PoolableSkinnableSample(ISampleInfo sampleInfo)
            : this()
        {
            Apply(sampleInfo);
        }

        /// <summary>
        /// Applies an <see cref="ISampleInfo"/> that describes the sample to retrieve.
        /// Only one <see cref="ISampleInfo"/> can ever be applied to a <see cref="PoolableSkinnableSample"/>.
        /// </summary>
        /// <param name="sampleInfo">The <see cref="ISampleInfo"/> to apply.</param>
        /// <exception cref="InvalidOperationException">If an <see cref="ISampleInfo"/> has already been applied to this <see cref="PoolableSkinnableSample"/>.</exception>
        public void Apply(ISampleInfo sampleInfo)
        {
            if (this.sampleInfo != null)
                throw new InvalidOperationException($"A {nameof(PoolableSkinnableSample)} cannot be applied multiple {nameof(ISampleInfo)}s.");

            this.sampleInfo = sampleInfo;

            Volume.Value = sampleInfo.Volume / 100.0;

            if (LoadState >= LoadState.Ready)
                updateSample();
        }

        protected override void SkinChanged(ISkinSource skin)
        {
            base.SkinChanged(skin);
            updateSample();
        }

        /// <summary>
        /// Whether this sample was playing before a skin source change.
        /// </summary>

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Return the sample to the pool (or dispose/recreate) before applying a new ISampleInfo.
  2. Ensure the pool resets sampleInfo to null on return; do not retain the instance across reuses.
  3. Use a fresh PoolableSkinnableSample per playback if pooling is unnecessary.
  4. Audit Apply call sites to confirm each is preceded by a pool Get (not a retained instance).

Example fix

// before
sample.Apply(infoA);
sample.Apply(infoB); // throws

// after
sample.Apply(infoA);
pool.Return(sample);
sample = pool.Get();
sample.Apply(infoB);
Defensive patterns

Strategy: validation

Validate before calling

if (sample.HasApplied)
    throw new InvalidOperationException("Sample already applied; return to pool first.");

Try / catch

try { sample.Apply(info); }
catch (InvalidOperationException) { sample = pool.Get(); sample.Apply(info); }

Prevention

When it happens

Trigger: Calling Apply(sampleInfo) twice on the same PoolableSkinnableSample instance without returning it to the pool (or before it was reset) — the second Apply hits the guard.

Common situations: Pooling bug where a sample is not properly returned/reset before reuse; calling Apply in a loop without pooling discipline; holding a reference to a pooled sample and reusing it across plays.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/8c592780e7bdb03f. Report an issue: GitHub.