@@ -83,7 +83,6 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
8383 }
8484 panic (fmt .Sprintf ("%q was URL for %q in nameToURL, but no corresponding srcGate in srcs map" , url , normalizedName ))
8585 }
86- sc .srcmut .RUnlock ()
8786
8887 // Without a direct match, we must fold the input name to a generally
8988 // stable, caseless variant and primarily work from that. This ensures that
@@ -103,6 +102,31 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
103102 // systems. So we follow this path, which is both a vastly simpler solution
104103 // and one that seems quite likely to work in practice.
105104 foldedNormalName := toFold (normalizedName )
105+ if foldedNormalName != normalizedName {
106+ // If the folded name differs from the input name, then there may
107+ // already be an entry for it in the nameToURL map, so check again.
108+ if url , has := sc .nameToURL [foldedNormalName ]; has {
109+ // There was a match on the canonical folded variant. Upgrade to a
110+ // write lock, so that future calls on this name don't need to
111+ // burn cycles on folding.
112+ sc .srcmut .RUnlock ()
113+ sc .srcmut .Lock ()
114+ // It may be possible that another goroutine could interleave
115+ // between the unlock and re-lock. Even if they do, though, they'll
116+ // only have recorded the same url value as we have here. In other
117+ // words, these operations commute, so we can safely write here
118+ // without checking again.
119+ sc .nameToURL [normalizedName ] = url
120+
121+ srcGate , has := sc .srcs [url ]
122+ sc .srcmut .Unlock ()
123+ if has {
124+ return srcGate , nil
125+ }
126+ panic (fmt .Sprintf ("%q was URL for %q in nameToURL, but no corresponding srcGate in srcs map" , url , normalizedName ))
127+ }
128+ }
129+ sc .srcmut .RUnlock ()
106130
107131 // No gateway exists for this path yet; set up a proto, being careful to fold
108132 // together simultaneous attempts on the same case-folded path.
@@ -140,7 +164,7 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
140164 sc .psrcmut .Unlock ()
141165 }
142166
143- pd , err := sc .deducer .deduceRootPath (ctx , normalizedName )
167+ pd , err := sc .deducer .deduceRootPath (ctx , foldedNormalName )
144168 if err != nil {
145169 // As in the deducer, don't cache errors so that externally-driven retry
146170 // strategies can be constructed.
@@ -189,15 +213,15 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
189213 defer sc .srcmut .Unlock ()
190214 // Record the name -> URL mapping, making sure that we also get the
191215 // self-mapping.
192- sc .nameToURL [normalizedName ] = url
193- if url != normalizedName {
216+ sc .nameToURL [foldedNormalName ] = url
217+ if url != foldedNormalName {
194218 sc .nameToURL [url ] = url
195219 }
196220
197221 // Make sure we have both the folded and unfolded names recorded in the map,
198222 // should they differ.
199223 if normalizedName != foldedNormalName {
200- sc .nameToURL [foldedNormalName ] = url
224+ sc .nameToURL [normalizedName ] = url
201225 }
202226
203227 if sa , has := sc .srcs [url ]; has {
0 commit comments