On 6/26/26 14:28, WenTao Liang wrote:
> dma_fence_get_stub() acquires an extra reference on the global stub
> fence, but this reference is never released on any execution path. The
> stub fence is filtered out inside dma_fence_unwrap_merge (already
> signaled), so the extra reference is never consumed. Both success and
> error paths fail to call dma_fence_put on the stub.
>
> Cc: stable(a)vger.kernel.org
> Fixes: 245a4a7b531c ("dma-buf: generalize dma_fence unwrap & merging v3")
Just drop that, the stub fence is a global dummy and leaking reference to it is harmless.
But just in case somebody uses this code as blueprint for this own implementation we should probably clean it up.
> Signed-off-by: WenTao Liang <vulab(a)iscas.ac.cn>
Reviewed-by: Christian König <christian.koenig(a)amd.com>
> ---
> drivers/dma-buf/st-dma-fence-unwrap.c | 11 +++++++----
> 1 file changed, 7 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dma-buf/st-dma-fence-unwrap.c b/drivers/dma-buf/st-dma-fence-unwrap.c
> index 72ca632e3981..b9ed85570211 100644
> --- a/drivers/dma-buf/st-dma-fence-unwrap.c
> +++ b/drivers/dma-buf/st-dma-fence-unwrap.c
> @@ -483,7 +483,7 @@ static int unwrap_merge_order(void *arg)
>
> static int unwrap_merge_complex(void *arg)
> {
> - struct dma_fence *fence, *f1, *f2, *f3, *f4, *f5;
> + struct dma_fence *fence, *f1, *f2, *f3, *f4, *f5, *stub;
> struct dma_fence_unwrap iter;
> int err = -ENOMEM;
>
> @@ -508,10 +508,11 @@ static int unwrap_merge_complex(void *arg)
> if (!f4)
> goto error_put_f3;
>
> + stub = dma_fence_get_stub();
> /* Signaled fences should be filtered, the two arrays merged. */
> - f5 = dma_fence_unwrap_merge(f3, f4, dma_fence_get_stub());
> + f5 = dma_fence_unwrap_merge(f3, f4, stub);
> if (!f5)
> - goto error_put_f4;
> + goto error_put_stub;
>
> err = 0;
> dma_fence_unwrap_for_each(fence, &iter, f5) {
> @@ -532,8 +533,10 @@ static int unwrap_merge_complex(void *arg)
> err = -EINVAL;
> }
>
> + dma_fence_put(stub);
> dma_fence_put(f5);
> -error_put_f4:
> +error_put_stub:
> + dma_fence_put(stub);
> dma_fence_put(f4);
> error_put_f3:
> dma_fence_put(f3);
On 6/26/26 14:21, WenTao Liang wrote:
> The success path only releases a2 via dma_fence_put but does not release
> a1, c1, or c2. The dma_fence_get calls at lines 440 and 445 were intended
> to pass references to mock_chain, but mock_chain already acquires its own
> references internally, making these extra gets surplus and permanently
> leaked.
>
> Cc: stable(a)vger.kernel.org
> Fixes: b1cce631e61f ("dma-buf: add selftest for fence order after merge")
Please drop that, this is a minor issue in a unit test and not anything which needs backporting.
> Signed-off-by: WenTao Liang <vulab(a)iscas.ac.cn>
> ---
> drivers/dma-buf/st-dma-fence-unwrap.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/drivers/dma-buf/st-dma-fence-unwrap.c b/drivers/dma-buf/st-dma-fence-unwrap.c
> index 9c74195f47fd..72ca632e3981 100644
> --- a/drivers/dma-buf/st-dma-fence-unwrap.c
> +++ b/drivers/dma-buf/st-dma-fence-unwrap.c
> @@ -472,6 +472,8 @@ static int unwrap_merge_order(void *arg)
> }
>
> dma_fence_put(a2);
> + dma_fence_put(c2);
> + dma_fence_put(a1);
That looks correct to me, but the error handler below is incorrect as well.
When c2 allocation fails we also need to release c1.
Regards,
Christian.
> return err;
>
> error_put_a1:
On 6/26/26 14:18, WenTao Liang wrote:
> dma_fence_enable_sw_signaling acquires an extra reference on each chain
> fence. The error unwind loop calls dma_fence_put only once per
> chain/fence without first signaling the fence to trigger the callback
> that releases the signaling reference. This prevents the chain fence kref
> from reaching 0, permanently leaking the chain and its contained fence.
>
> Cc: stable(a)vger.kernel.org
> Fixes: dc2f7e67a28a ("dma-buf: Exercise dma-fence-chain under selftests")
Please drop that, this is a minor issue in a unit test and not anything which needs backporting.
> Signed-off-by: WenTao Liang <vulab(a)iscas.ac.cn>
> ---
> drivers/dma-buf/st-dma-fence-chain.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma-buf/st-dma-fence-chain.c b/drivers/dma-buf/st-dma-fence-chain.c
> index 821023dd34df..7dc18e294387 100644
> --- a/drivers/dma-buf/st-dma-fence-chain.c
> +++ b/drivers/dma-buf/st-dma-fence-chain.c
> @@ -152,7 +152,10 @@ static int fence_chains_init(struct fence_chains *fc, unsigned int count,
>
> unwind:
> for (i = 0; i < count; i++) {
> - dma_fence_put(fc->fences[i]);
> + if (fc->fences[i]) {
> + dma_fence_signal(fc->fences[i]);
> + dma_fence_put(fc->fences[i]);
> + }
The usual text book idiom for such cleanup cases is:
while (i--) {
dma_fence_signal(fc->fences[i]);
dma_fence_put(fc->chains[i]);
}
Additional to that we need a different error handling target for the case that the mock_chain() allocation fails (or just do another dma_fence_put there).
Regards,
Christian.
> dma_fence_put(fc->chains[i]);
> }
> kvfree(fc->fences);
UDMABUF_CREATE_LIST copies an array whose element count comes from
userspace. The count is compared against list_limit, but list_limit is a
signed module parameter while the count is u32.
If the limit is raised too far or made negative, that comparison no
longer bounds the count to a range where sizeof(*list) * count fits in
the u32 temporary used for the copy length. A wrapped copy length lets
memdup_user() copy fewer entries than udmabuf_create() subsequently
walks, leading to out-of-bounds reads from the copied list.
Take a positive snapshot of the module limit and use memdup_array_user()
so the multiplication is checked before copying.
Signed-off-by: Yousef Alhouseen <alhouseenyousef(a)gmail.com>
---
drivers/dma-buf/udmabuf.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/dma-buf/udmabuf.c b/drivers/dma-buf/udmabuf.c
index bced421c0..b4078ec84 100644
--- a/drivers/dma-buf/udmabuf.c
+++ b/drivers/dma-buf/udmabuf.c
@@ -469,14 +469,15 @@ static long udmabuf_ioctl_create_list(struct file *filp, unsigned long arg)
struct udmabuf_create_list head;
struct udmabuf_create_item *list;
int ret = -EINVAL;
- u32 lsize;
+ int limit;
if (copy_from_user(&head, (void __user *)arg, sizeof(head)))
return -EFAULT;
- if (head.count > list_limit)
+ limit = READ_ONCE(list_limit);
+ if (!head.count || limit <= 0 || head.count > limit)
return -EINVAL;
- lsize = sizeof(struct udmabuf_create_item) * head.count;
- list = memdup_user((void __user *)(arg + sizeof(head)), lsize);
+ list = memdup_array_user((void __user *)(arg + sizeof(head)),
+ head.count, sizeof(*list));
if (IS_ERR(list))
return PTR_ERR(list);
--
2.54.0
The commit mentioned in the fixes tag below introduced a mechanism
through which fence producers can fully decouple from fence consumers.
This, desirable, mechanism is based on the fence's signaled-bit as the
"decoupling point".
A sophisticated interaction between RCU and atomic instructions attempts
to ensure that fence consumers can still interact with fence producers
through the dma_fence_ops, callback pointers into the producer.
This is the desired behavior: to check for decoupling, the signaled-bit
is first checked. If it's not yet signaled, RCU ensures that the ops
pointer cannot yet be NULL.
Hereby, dma_fence_signal_timestamp_locked() first sets the signaled-bit,
and then sets the ops pointer to NULL. Readers first load the ops
pointer, and then check through the signaled-bit whether the pointer can
legally be accessed.
These set and load operations could occur out of order on weakly ordered
platforms. Hence, we need to enforce strict ordering all the time.
Add the appropriate memory barriers.
Cc: stable(a)vger.kernel.org
Fixes: f4cc3ab824d6 ("dma-buf: protected fence ops by RCU v8")
Signed-off-by: Philipp Stanner <phasta(a)kernel.org>
---
Tested with dmabuf and drm_sched unit tests.
Memory barriers are notoriously difficult, so I would appreciate if some
of the more experienced folks can check this. Notably, I am not sure
whether the smp_wmb() is necessary.
The documentation for test_and_set_bit() makes the mysterious statement
"This is an atomic fully-ordered operation (implied full memory
barrier)", but the kcsan_mb() seems to be some sort of debugging
barrier, and in any case the docu doesn't make it obvious to me whether
that "full barrier" comes before or after the bit setting takes place.
Moreover, in my opinion we should order dma_fence_is_signaled(), too –
but if we agree to merge Christian's new series [1] that need should
disappear.
[1] https://lore.kernel.org/dri-devel/20260624122917.2483-1-christian.koenig@am…
---
drivers/dma-buf/dma-fence.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
index c7ea1e75d38a..2e80b01499de 100644
--- a/drivers/dma-buf/dma-fence.c
+++ b/drivers/dma-buf/dma-fence.c
@@ -363,6 +363,18 @@ void dma_fence_signal_timestamp_locked(struct dma_fence *fence,
&fence->flags)))
return;
+ /*
+ * Fully order setting of the bit above with setting of the ops pointer
+ * to NULL below, so that all parties can use the signaled flag to
+ * detect that the fence decoupled from its ops in a safe manner.
+ *
+ * The counter parts of this barrier are in dma_fence_timeline_name()
+ * and dma_fence_driver_name(). All other future parties that rely on
+ * the signaled flag for valid access to the ops pointer will need a
+ * memory barrier.
+ */
+ smp_wmb();
+
trace_dma_fence_signaled(fence);
/*
@@ -1170,6 +1182,12 @@ const char __rcu *dma_fence_driver_name(struct dma_fence *fence)
/* RCU protection is required for safe access to returned string */
ops = rcu_dereference(fence->ops);
+ /*
+ * Fully order the dereference above with the flag check. Otherwise,
+ * ops could be dereferenced as a NULL pointer. The barrier's
+ * counterpart is in dma_fence_signal_timestamp_locked().
+ */
+ smp_rmb();
if (!dma_fence_test_signaled_flag(fence))
return (const char __rcu *)ops->get_driver_name(fence);
else
@@ -1203,6 +1221,12 @@ const char __rcu *dma_fence_timeline_name(struct dma_fence *fence)
/* RCU protection is required for safe access to returned string */
ops = rcu_dereference(fence->ops);
+ /*
+ * Fully order the dereference above with the flag check. Otherwise,
+ * ops could be dereferenced as a NULL pointer. The barrier's
+ * counterpart is in dma_fence_signal_timestamp_locked().
+ */
+ smp_rmb();
if (!dma_fence_test_signaled_flag(fence))
return (const char __rcu *)ops->get_driver_name(fence);
else
base-commit: cdeb2ccd993ed8647adbbda2c3b103aa717fd6f7
--
2.54.0
The entity->last_scheduled field has always been set and read with
special RCU functions in addition to memory barriers. There is no
obvious reason for that, since the entity lock is available and taken at
all places that evaluate the last_scheduled field. The only exception is
drm_sched_entity_error(), which is not performance critical in any way.
Improve robustness, readability and maintainability by replacing RCU and
barriers with the lock.
As a preparational step, while at it, also guard spsc_queue_pop() with
the lock, since spsc_queue is deprecated and supposed to be replaced
with a locked list.
Signed-off-by: Philipp Stanner <phasta(a)kernel.org>
---
Changes since v1:
- Add a helper variable to drop the last_scheduled reference without
the entity lock being held; just to be more robust.
- Write additional comment to detail the WRITE_ONCE().
---
drivers/gpu/drm/scheduler/sched_entity.c | 58 +++++++++++++-----------
include/drm/gpu_scheduler.h | 9 ++--
2 files changed, 35 insertions(+), 32 deletions(-)
diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c
index c51101ec70c1..12fd695c6d46 100644
--- a/drivers/gpu/drm/scheduler/sched_entity.c
+++ b/drivers/gpu/drm/scheduler/sched_entity.c
@@ -135,7 +135,6 @@ int drm_sched_entity_init(struct drm_sched_entity *entity,
entity->num_sched_list = num_sched_list;
entity->sched_list = num_sched_list > 1 ? sched_list : NULL;
entity->rq = &sched_list[0]->rq;
- RCU_INIT_POINTER(entity->last_scheduled, NULL);
RB_CLEAR_NODE(&entity->rb_tree_node);
init_completion(&entity->entity_idle);
@@ -201,10 +200,10 @@ int drm_sched_entity_error(struct drm_sched_entity *entity)
struct dma_fence *fence;
int r;
- rcu_read_lock();
- fence = rcu_dereference(entity->last_scheduled);
+ spin_lock(&entity->lock);
+ fence = entity->last_scheduled;
r = fence ? fence->error : 0;
- rcu_read_unlock();
+ spin_unlock(&entity->lock);
return r;
}
@@ -288,8 +287,10 @@ void drm_sched_entity_kill(struct drm_sched_entity *entity)
wait_for_completion(&entity->entity_idle);
/* The entity is guaranteed to not be used by the scheduler */
- prev = rcu_dereference_check(entity->last_scheduled, true);
+ spin_lock(&entity->lock);
+ prev = entity->last_scheduled;
dma_fence_get(prev);
+ spin_unlock(&entity->lock);
while ((job = drm_sched_entity_queue_pop(entity))) {
struct drm_sched_fence *s_fence = job->s_fence;
@@ -381,8 +382,12 @@ void drm_sched_entity_fini(struct drm_sched_entity *entity)
entity->dependency = NULL;
}
- dma_fence_put(rcu_dereference_check(entity->last_scheduled, true));
- RCU_INIT_POINTER(entity->last_scheduled, NULL);
+ dma_fence_put(entity->last_scheduled);
+ /*
+ * Normally all users should be gone now, but since drm_sched has
+ * experienced many layering violations in the past, better be safe.
+ */
+ WRITE_ONCE(entity->last_scheduled, NULL);
drm_sched_entity_stats_put(entity->stats);
}
EXPORT_SYMBOL(drm_sched_entity_fini);
@@ -507,6 +512,10 @@ drm_sched_job_dependency(struct drm_sched_job *job,
struct drm_sched_job *drm_sched_entity_pop_job(struct drm_sched_entity *entity)
{
+ /* Helper to avoid dropping the reference while the entity lock is held,
+ * just to have some more robustness.
+ */
+ struct dma_fence *prev_last_scheduled;
struct drm_sched_job *sched_job;
sched_job = drm_sched_entity_queue_peek(entity);
@@ -523,19 +532,20 @@ struct drm_sched_job *drm_sched_entity_pop_job(struct drm_sched_entity *entity)
if (entity->guilty && atomic_read(entity->guilty))
dma_fence_set_error(&sched_job->s_fence->finished, -ECANCELED);
- dma_fence_put(rcu_dereference_check(entity->last_scheduled, true));
- rcu_assign_pointer(entity->last_scheduled,
- dma_fence_get(&sched_job->s_fence->finished));
+ spin_lock(&entity->lock);
+ prev_last_scheduled = entity->last_scheduled;
+ entity->last_scheduled = dma_fence_get(&sched_job->s_fence->finished);
- /*
- * If the queue is empty we allow drm_sched_entity_select_rq() to
- * locklessly access ->last_scheduled. This only works if we set the
- * pointer before we dequeue and if we a write barrier here.
+ /* A recent rework required taking the spinlock above. Since spsc_queue
+ * is scheduled for removal as per the DRM-TODO-list, we access it here
+ * locked already to prepare for that cleanup.
+ *
+ * TODO: Fully replace spsc_queue with a locked (h)list.
*/
- smp_wmb();
-
spsc_queue_pop(&entity->job_queue);
+ spin_unlock(&entity->lock);
+ dma_fence_put(prev_last_scheduled);
drm_sched_rq_pop_entity(entity);
/* Jobs and entities might have different lifecycles. Since we're
@@ -561,21 +571,15 @@ void drm_sched_entity_select_rq(struct drm_sched_entity *entity)
if (spsc_queue_count(&entity->job_queue))
return;
- /*
- * Only when the queue is empty are we guaranteed that
- * drm_sched_run_job_work() cannot change entity->last_scheduled. To
- * enforce ordering we need a read barrier here. See
- * drm_sched_entity_pop_job() for the other side.
- */
- smp_rmb();
-
- fence = rcu_dereference_check(entity->last_scheduled, true);
+ spin_lock(&entity->lock);
+ fence = entity->last_scheduled;
/* stay on the same engine if the previous job hasn't finished */
- if (fence && !dma_fence_is_signaled(fence))
+ if (fence && !dma_fence_is_signaled(fence)) {
+ spin_unlock(&entity->lock);
return;
+ }
- spin_lock(&entity->lock);
sched = drm_sched_pick_best(entity->sched_list, entity->num_sched_list);
rq = sched ? &sched->rq : NULL;
if (rq != entity->rq) {
diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h
index d61c19e78182..176ff1f936cd 100644
--- a/include/drm/gpu_scheduler.h
+++ b/include/drm/gpu_scheduler.h
@@ -100,7 +100,8 @@ struct drm_sched_entity {
* @lock:
*
* Lock protecting the run-queue (@rq) to which this entity belongs,
- * @priority and the list of schedulers (@sched_list, @num_sched_list).
+ * @priority, @last_scheduled and the list of schedulers (@sched_list,
+ * @num_sched_list).
*/
spinlock_t lock;
@@ -202,11 +203,9 @@ struct drm_sched_entity {
/**
* @last_scheduled:
*
- * Points to the finished fence of the last scheduled job. Only written
- * by drm_sched_entity_pop_job(). Can be accessed locklessly from
- * drm_sched_job_arm() if the queue is empty.
+ * Points to the finished fence of the last scheduled job.
*/
- struct dma_fence __rcu *last_scheduled;
+ struct dma_fence *last_scheduled;
/**
* @last_user: last group leader pushing a job into the entity.
--
2.54.0
Have you ever found yourself staring at a blank screen, wondering what new world you could create with just a few simple elements? Or perhaps you’ve dreamed of combining the mundane to forge the fantastical? If so, then get ready to dive into the endlessly fascinating world of Infinite Craft. This browser-based game, developed by Neal Agarwal, isn't about high scores or complex strategies; it's about pure, unadulterated creativity and the joy of discovery.
https://infinitecrafts.io
What is Infinite Craft?
At its heart, Infinite Craft is a deceptively simple yet profoundly engaging puzzle game. You start with four fundamental elements: Earth, Wind, Fire, and Water. Your goal? To combine these elements, and the new ones you create, to unlock an ever-expanding universe of concepts, objects, and even abstract ideas. There's no tutorial, no predefined path, just you and your imagination.
How to Play (or Experience) Infinite Craft
Playing Infinite Craft is as straightforward as it gets. When you load the game, you'll see your four starting elements on the right-hand side of the screen. The main area is your canvas. To begin, simply drag one element onto another. For example, dragging "Water" onto "Water" might yield "Lake." Dragging "Fire" onto "Earth" could create "Lava."
The real magic happens when you start combining the results. Take "Lake" and drag "Fire" onto it, and you might get "Steam." Combine "Steam" with "Wind," and perhaps "Cloud" emerges. The beauty of the game lies in its unpredictable and often humorous outcomes. You can stumble upon anything from "Plant" and "Animal" to "Universe" and "Time Travel." You might even create "Doge" or "ChatGPT" – the possibilities truly seem endless. Each successful combination adds the new element to your growing list on the right, ready to be used in further experimentation.
Tips for Aspiring Alchemists
Experiment Fearlessly: Don't be afraid to try seemingly nonsensical combinations. Often, the most unexpected pairings lead to breakthroughs. What happens when you combine "Human" and "Computer"? You might be surprised!
Think Incrementally: If you're aiming for a complex concept, try to break it down into its constituent parts. Want to make "Tree"? You'll likely need "Plant" and perhaps "Wood."
Utilize Your Existing Elements: As your list grows, scan through it for new inspiration. A previously created element might be the missing ingredient for your next discovery.
Don't Overthink It: The joy of Infinite Craft is in the process of discovery, not in reaching a specific goal. Let your curiosity guide you.
Keep it Organized (Optional): While there's no official way to organize your elements, some players find it helpful to mentally categorize them or focus on creating a specific chain of related items.
Conclusion
Infinite Craft is more than just a game; it's a digital sandbox for your mind. It's a testament to the power of simple mechanics to spark immense creativity and provide hours of engaging exploration. Whether you're looking for a quick five-minute distraction or a deep dive into the art of elemental combination, Infinite Craft offers a unique and endlessly rewarding experience. So go ahead, start combining, and see what wonders you can conjure!
“Wait, did you hear that?”
That sentence probably sums up half of the repo experience.
Not the loot. Not the monsters. Not even the objective. Just a panicked voice in the dark, followed by four people freezing at once because nobody wants to be the first one to move. That’s the strange power of repo. It doesn’t only scare you with what’s on the screen. It scares you through your friends, through bad communication, through the silence between footsteps, and through the one teammate who always makes noise at the worst possible moment.
Play now: https://repoonlinegame.com
By 2026, a lot of multiplayer horror games have started to blur together. Some rely too heavily on screaming content creators. Others feel scary for an hour, then turn into routine once you understand the AI patterns. So it’s fair to ask: is repo still scary with friends, or has it become one of those games that’s more funny than frightening once the novelty wears off?
After spending enough time in repo to know exactly how a “quick run” turns into a 90-minute disaster, I’d say the answer is a little more interesting than yes or no. Repo is still scary, but not in the same way as a solo horror game. Its fear changes shape when you play with other people. Sometimes it becomes weaker. Sometimes it becomes much stronger. And weirdly, the funniest moments are often the reason the horror still works.
Why Repo Feels Different From Solo Horror
Repo stays scary with friends because its fear doesn’t depend only on isolation. It depends on uncertainty, noise, teamwork pressure, and the feeling that your group can collapse at any second. That makes it a very different kind of horror experience.
In a solo horror game, the fear is usually direct. You’re alone, vulnerable, and forced to process every threat by yourself. In repo, you’re technically safer because you have teammates. But that safety is unstable. It can disappear in seconds.
Friends make you feel safer, but only at first
The first few rounds of repo can trick you into thinking co-op removes the fear. If four people are exploring together, how bad can it be?
Pretty bad, actually.
Having teammates helps, but it also creates new problems. Someone makes noise. Someone gets separated. Someone panics and gives bad information. Someone carries the wrong item at the wrong time and slows everyone down. In a solo horror game, your mistakes are your own. In repo, the scariest situations often come from a chain reaction of other people’s mistakes.
That’s why repo multiplayer horror game sessions feel so unpredictable. You’re not only managing monsters or map hazards. You’re managing human chaos.
The tension comes from shared responsibility
A lot of horror games are built around survival. Repo adds another layer by making your team responsible for loot, movement, and extraction. You’re not just trying to stay alive. You’re trying to succeed together.
That changes the emotional pressure.
When you’re carrying something valuable and your team is counting on you not to mess it up, even a small noise can spike your stress. You stop thinking like a horror player and start thinking like someone trying not to be the reason the run fails. That’s a very effective kind of fear because it mixes danger with embarrassment.
You’re never fully in control
This is one of the smartest things about repo. Even when you understand the game better, you rarely feel fully comfortable. A familiar map doesn’t remove the pressure. It just changes how you prepare for it.
The unpredictability of your team keeps the experience alive. That matters in 2026, when many horror games lose their edge once players learn the systems. Repo avoids that because the biggest variable is not the monster. It’s the people beside you.
Is Repo More Funny Than Scary With Friends?
Yes, repo is often hilarious with friends, but that doesn’t cancel out the horror. It actually strengthens it. The comedy makes players relax just enough for the next scare to hit harder.
That balance is one of the main reasons repo works so well.
Panic makes everything louder and dumber
The best funniest repo moments with friends usually happen when a scary situation is already in progress. Nobody is calm. Nobody is speaking clearly. One person is giving instructions, another is apologizing, and a third is making the situation worse in real time.
That chaos is funny because it feels real.
In a lot of co-op horror games, the comedy comes from ragdoll physics or random bugs. In repo, the humor often comes from decision-making under pressure. Players say ridiculous things because their brains are overloaded. They blame each other for mistakes they all contributed to. They whisper like survival experts and then immediately sprint into danger.
The result is a game that feels social without losing its horror identity.
Proximity voice chat changes everything
If there’s one system that keeps repo tense even after multiple sessions, it’s proximity voice chat. This feature does more than add immersion. It changes how fear travels through the group.
When players are separated, communication becomes messy. Someone hears a warning too late. Someone talks too quietly. Someone screams from another room, and nobody knows if they’re joking or dying. That uncertainty is gold for horror.
It also creates an incredible rhythm. Silence becomes threatening. Distant voices become clues. The map feels bigger because your team no longer exists as one safe unit. You’re close enough to help, but not always close enough to fix the problem.
Humor lowers your guard
This is why repo can still scare experienced players. The game gives you moments to laugh, and those moments reduce your tension just enough to make the next threat land properly.
You stop bracing for horror because your friend is busy telling a terrible plan.
Then a sound interrupts the joke.
Now everybody is quiet.
Now the room feels dangerous again.
That emotional whiplash is part of what makes repo so memorable. The game doesn’t stay at one intensity level. It keeps shifting between panic and relief, and that movement makes both emotions stronger.
What Actually Makes Repo Scary in 2026?
Repo remains scary in 2026 because it builds fear through vulnerability, sound, and teamwork failure rather than relying only on cheap jump scares. Even when players know the basics, the game still creates tension through unstable group dynamics.
That’s important because horror doesn’t age well if surprise is the only tool. Once you know the tricks, the fear disappears. Repo survives longer because its tension is systemic.
Sound design keeps the pressure alive
A good horror game knows how to use silence, distant movement, and environmental noise. Repo leans into all of that. The soundscape does a lot of the heavy lifting, especially when your team is already on edge.
You hear movement and stop talking.
You hear a teammate whisper and assume the worst.
You hear something drop in another room and instantly imagine three different disasters.
That constant low-level audio tension makes repo feel alive, even when nothing dramatic is happening.
Movement and carrying create vulnerability
One reason repo works better than many co-op horror games is that the extraction mechanics create physical awkwardness. You’re not always free to move fast, react instantly, or reposition safely. Carrying loot changes how you navigate danger.
That’s a huge part of the stress.
When players are forced to commit to movement, every bad decision feels heavier. You can’t always run cleanly. You can’t always recover from a mistake. That friction creates tension without needing a scripted scare.
Teamwork can break at the worst time
This is where semi-coop horror becomes especially effective. Even if everyone technically shares the same goal, people still make selfish or careless choices. Not because they’re malicious, but because panic makes players unreliable.
And honestly, that’s terrifying.
A teammate who runs off with valuable loot.
A friend who insists it’s safe when it clearly isn’t.
A player who freezes and blocks a doorway.
A rushed extraction where nobody agrees on the plan.
These are not cinematic scares. They’re social ones. But they hit hard because they feel plausible every single round.
Repo With Friends vs Other Co-op Horror Games
Repo still stands out in 2026 because it turns ordinary co-op mistakes into the core of the horror. It doesn’t just place scary things in front of players. It lets players create the tension themselves.
Here’s where it differs from a lot of similar games:
Repo uses teamwork gameplay as a source of fear, not just a solution
Proximity voice chat matters mechanically, not just socially
The extraction mechanics force players to take risks under pressure
Comedy happens naturally without turning the whole game into parody
The fear lasts longer because your teammates stay unpredictable
That last point matters most.
Most horror games stop being scary once the player learns the map, the monster, or the objective. Repo still has room to surprise because your group keeps changing the shape of every run. One night your team is careful and coordinated. The next night it’s a complete disaster. Same game, totally different emotional experience.
So, Is Repo Worth Playing in 2026 If You Have Friends?
Yes, repo is worth playing in 2026, especially if you want a horror game that stays tense through social chaos rather than pure isolation. If your favorite horror memories involve shouting at friends, failing together, and somehow laughing through panic, repo absolutely delivers.
That doesn’t mean it will scare every group equally. Some teams will turn it into a comedy night. Others will play cautiously and make it much more intense. But that flexibility is part of the appeal. Repo doesn’t force one mood. It gives players a system where fear and humor can keep feeding each other.
Repo is a great fit if you enjoy:
Co-op horror with strong communication pressure
Unpredictable friend-group gameplay
Indie horror with personality instead of polished sameness
Games where failure becomes part of the fun
Tense looting and extraction systems
You may bounce off Repo if:
You only want pure solo fear
You dislike messy teamwork and loud multiplayer sessions
You prefer heavily scripted horror over emergent moments
You want a game that stays serious all the time
Personally, I think that’s exactly why repo has stayed relevant. It understands that horror with friends doesn’t need to copy solo horror. It needs to create a different kind of pressure. Less lonely. More chaotic. More embarrassing. Sometimes even more memorable.
And when the game is working at its best, that pressure is very real.
Final Thoughts: Is Repo Still Scary With Friends?
Yes, repo is still scary with friends in 2026, but its fear comes from a different place than traditional horror. It’s not only about what hunts you in the dark. It’s about what happens when four people try to stay calm, carry valuable loot, and communicate under pressure without falling apart.
That’s what makes repo special.
It’s scary because your team can fail.
It’s funny because your team probably will.
And somehow, those two things make each other stronger.
If you’re asking is repo worth playing in 2026, the answer is easy if you’ve got the right group. Bring friends who like horror, bad plans, and yelling “I said don’t move” into voice chat. Repo will handle the rest.
FAQ
1. Is repo still scary after a few hours?
Yes, because repo relies on teamwork mistakes, sound tension, and unpredictable group behavior. Even when the mechanics become familiar, the people around you keep the horror fresh.
2. Is repo better solo or with friends?
Repo is much better with friends for most players. The game’s best moments come from proximity voice chat, teamwork failure, and shared panic during extraction.
3. What makes repo different from other co-op horror games?
Repo stands out by combining semi-coop horror, jump scares, physical loot handling, and social chaos into one replayable multiplayer loop.
Most of this patch series has already been pushed upstream, this is just
the second half of the patch series that has not been pushed yet + some
additional changes which were required to implement changes requested by
the mailing list. This patch series is originally from Asahi, previously
posted by Daniel Almeida.
The previous version of the patch series can be found here:
https://patchwork.freedesktop.org/series/164580/
Branch with patches applied available here:
https://gitlab.freedesktop.org/lyudess/linux/-/commits/rust/gem-shmem
This patch series applies on top of drm-rust-next
Patch-series wide changes since V15:
* Fix some major rebasing errors I somehow didn't notice :(
* Drop the dependency on LazyInit, use the trick that Alice suggested
instead.
* Fix dependency ordering so that Tyr can get the vmap stuff first
without the other bits.
Patch-series wide changes since V16:
* Fix ordering one more time (SetOnce::reset() doesn't need to come
before adding vmap functions)
* Rebase against the latest DeviceContext changes from me that got
pushed.
Patch-series wide changes since V20:
* Lots of Sashiko fixes, excluding the comments that I couldn't prove
weren't just bogus.
Lyude Paul (4):
rust: drm: gem: shmem: Add DmaResvGuard helper
rust: drm: gem: shmem: Add vmap functions
rust: faux: Allow retrieving a bound Device
rust: drm: gem: Introduce shmem::Object::sg_table()
rust/kernel/drm/gem/shmem.rs | 547 ++++++++++++++++++++++++++++++++++-
rust/kernel/faux.rs | 18 +-
2 files changed, 549 insertions(+), 16 deletions(-)
base-commit: 550dc7536644db2d67c6f8cf525bba682fba08d9
--
2.54.0
dma_fence_timeline_name() incorrectly invokes ops->get_driver_name()
instead of ops->get_timeline_name(), so every caller receives the
driver name where the timeline name was expected.
This is a copy-paste regression that has resurfaced twice. It was
originally introduced by commit 62918542b7bf ("dma-fence: Fix sparse
warnings due __rcu annotations") when adding the __rcu casts, fixed
by commit 033559473dd3 ("dma-fence: Fix safe access wrapper to call
timeline name method"), and then accidentally reintroduced by commit
e58b4dea9054 ("dma-buf/dma-fence: Add dma_fence_test_signaled_flag()")
when both wrappers were refactored to use the new helper.
Signed-off-by: Baineng Shou <shoubaineng(a)gmail.com>
---
drivers/dma-buf/dma-fence.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c
index b3bfa6943a8e..5292d714419b 100644
--- a/drivers/dma-buf/dma-fence.c
+++ b/drivers/dma-buf/dma-fence.c
@@ -1202,7 +1202,7 @@ const char __rcu *dma_fence_timeline_name(struct dma_fence *fence)
/* RCU protection is required for safe access to returned string */
ops = rcu_dereference(fence->ops);
if (!dma_fence_test_signaled_flag(fence))
- return (const char __rcu *)ops->get_driver_name(fence);
+ return (const char __rcu *)ops->get_timeline_name(fence);
else
return (const char __rcu *)"signaled-timeline";
}
--
2.34.1