@@ -703,10 +703,11 @@ protected void setModuleSuperClass(RubyClass superClass) {
703703 }
704704
705705 public Collection subclasses (boolean includeDescendants ) {
706- if (subclasses != null ) {
707- Collection <RubyClass > mine = new ArrayList <RubyClass >(subclasses );
706+ Set <RubyClass > mySubclasses = subclasses ;
707+ if (mySubclasses != null ) {
708+ Collection <RubyClass > mine = new ArrayList <RubyClass >(mySubclasses );
708709 if (includeDescendants ) {
709- for (RubyClass i : subclasses ) {
710+ for (RubyClass i : mySubclasses ) {
710711 mine .addAll (i .subclasses (includeDescendants ));
711712 }
712713 }
@@ -716,21 +717,63 @@ public Collection subclasses(boolean includeDescendants) {
716717 return Collections .EMPTY_LIST ;
717718 }
718719 }
719-
720+
721+ /**
722+ * Add a new subclass to the weak set of subclasses.
723+ *
724+ * This version always constructs a new set to avoid having to synchronize
725+ * against the set when iterating it for invalidation in
726+ * invalidateCacheDescendants.
727+ *
728+ * @param subclass The subclass to add
729+ */
720730 public synchronized void addSubclass (RubyClass subclass ) {
721- if (subclasses == null ) subclasses = new WeakHashSet <RubyClass >();
722- subclasses .add (subclass );
731+ Set <RubyClass > oldSubclasses = subclasses ;
732+ Set <RubyClass > mySubclasses =
733+ new WeakHashSet <RubyClass >(oldSubclasses == null ? 1 : oldSubclasses .size () + 1 );
734+ if (oldSubclasses != null ) mySubclasses .addAll (oldSubclasses );
735+ mySubclasses .add (subclass );
736+ subclasses = Collections .unmodifiableSet (mySubclasses );
723737 }
724738
739+ /**
740+ * Remove a subclass from the weak set of subclasses.
741+ *
742+ * This version always constructs a new set to avoid having to synchronize
743+ * against the set when iterating it for invalidation in
744+ * invalidateCacheDescendants.
745+ *
746+ * @param subclass The subclass to remove
747+ */
725748 public synchronized void removeSubclass (RubyClass subclass ) {
726- if (subclasses == null ) return ;
727- subclasses .remove (subclass );
749+ Set <RubyClass > oldSubclasses = subclasses ;
750+ if (oldSubclasses == null ) return ;
751+
752+ Set <RubyClass > mySubclasses = new WeakHashSet <RubyClass >(oldSubclasses .size () + 1 );
753+ mySubclasses .addAll (oldSubclasses );
754+ mySubclasses .remove (subclass );
755+
756+ subclasses = Collections .unmodifiableSet (mySubclasses );
728757 }
729758
759+ /**
760+ * Invalidate all subclasses of this class by walking the set of all
761+ * subclasses and asking them to invalidate themselves.
762+ *
763+ * Note that this version works against a reference to the current set of
764+ * subclasses, which could be replaced by the time this iteration is
765+ * complete. In theory, there may be a path by which invalidation would
766+ * miss a class added during the invalidation process, but the exposure is
767+ * minimal if it exists at all. The only way to prevent it would be to
768+ * synchronize both invalidation and subclass set modification against a
769+ * global lock, which we would like to avoid.
770+ */
771+ @ Override
730772 protected void invalidateCacheDescendants () {
731773 super .invalidateCacheDescendants ();
732774 // update all subclasses
733- if (subclasses != null ) for (RubyClass subclass : subclasses ) {
775+ Set <RubyClass > mySubclasses = subclasses ;
776+ if (mySubclasses != null ) for (RubyClass subclass : mySubclasses ) {
734777 subclass .invalidateCacheDescendants ();
735778 }
736779 }
0 commit comments