--- a/net/minecraft/world/entity/projectile/Projectile.java
+++ b/net/minecraft/world/entity/projectile/Projectile.java
@@ -38,7 +_,7 @@
     @Nullable
     public UUID ownerUUID;
     @Nullable
-    public Entity cachedOwner;
+    public org.bukkit.craftbukkit.entity.CraftEntity cachedOwner; // Folia - region threading - replace with CraftEntity
     public boolean leftOwner;
     public boolean hasBeenShot;
     @Nullable
@@ -52,7 +_,7 @@
     public void setOwner(@Nullable Entity owner) {
         if (owner != null) {
             this.ownerUUID = owner.getUUID();
-            this.cachedOwner = owner;
+            this.cachedOwner = owner.getBukkitEntity(); // Folia - region threading
         }
         // Paper start - Refresh ProjectileSource for projectiles
         else {
@@ -69,22 +_,38 @@
         if (fillCache) {
             this.getOwner();
         }
-        if (this.cachedOwner != null && !this.cachedOwner.isRemoved() && this.projectileSource == null && this.cachedOwner.getBukkitEntity() instanceof org.bukkit.projectiles.ProjectileSource projSource) {
+        if (this.cachedOwner != null && !this.cachedOwner.getHandleRaw().isRemoved() && this.projectileSource == null && this.cachedOwner instanceof org.bukkit.projectiles.ProjectileSource projSource) { // Folia - region threading
             this.projectileSource = projSource;
         }
     }
     // Paper end - Refresh ProjectileSource for projectiles
 
+    // Folia start - region threading
+    // In general, this is an entire mess. At the time of writing, there are fifty usages of getOwner.
+    // Usage of this function is to avoid concurrency issues, even if it sacrifices behavior.
     @Nullable
     @Override
     public Entity getOwner() {
-        if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) {
+        Entity ret = this.getOwnerRaw();
+        return ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(ret) && (ret == null || !ret.isRemoved()) ? ret : null;
+    }
+    // Folia end - region threading
+
+    @Nullable
+    public Entity getOwnerRaw() { // Folia - region threading
+        ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot update owner state asynchronously"); // Folia - region threading
+        if (this.cachedOwner != null && !this.cachedOwner.isPurged()) { // Folia - region threading
             this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles
-            return this.cachedOwner;
+            return this.cachedOwner.getHandleRaw(); // Folia - region threading
         } else if (this.ownerUUID != null) {
-            this.cachedOwner = this.findOwner(this.ownerUUID);
+            // Folia start - region threading
+            Entity ret = this.findOwner(this.ownerUUID);
+            if (ret != null) {
+                this.cachedOwner = ret.getBukkitEntity();
+            }
+            // Folia end - region threading
             this.refreshProjectileSource(false); // Paper - Refresh ProjectileSource for projectiles
-            return this.cachedOwner;
+            return ret; // Folia - region threading
         } else {
             return null;
         }
@@ -130,7 +_,12 @@
     protected void setOwnerThroughUUID(UUID uuid) {
         if (this.ownerUUID != uuid) {
             this.ownerUUID = uuid;
-            this.cachedOwner = this.findOwner(uuid);
+            // Folia start - region threading
+            Entity cachedOwner = this.findOwner(this.ownerUUID);
+            if (cachedOwner != null) {
+                this.cachedOwner = cachedOwner.getBukkitEntity();
+            }
+            // Folia end - region threading
         }
     }
 
@@ -454,7 +_,7 @@
     @Override
     public boolean mayInteract(ServerLevel level, BlockPos pos) {
         Entity owner = this.getOwner();
-        return owner instanceof Player ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+        return owner instanceof Player && ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(owner) ? owner.mayInteract(level, pos) : owner == null || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Folia - region threading
     }
 
     public boolean mayBreak(ServerLevel level) {
