@@ -83,7 +83,6 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
83
83
}
84
84
panic (fmt .Sprintf ("%q was URL for %q in nameToURL, but no corresponding srcGate in srcs map" , url , normalizedName ))
85
85
}
86
- sc .srcmut .RUnlock ()
87
86
88
87
// Without a direct match, we must fold the input name to a generally
89
88
// stable, caseless variant and primarily work from that. This ensures that
@@ -103,6 +102,31 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
103
102
// systems. So we follow this path, which is both a vastly simpler solution
104
103
// and one that seems quite likely to work in practice.
105
104
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 ()
106
130
107
131
// No gateway exists for this path yet; set up a proto, being careful to fold
108
132
// together simultaneous attempts on the same case-folded path.
@@ -140,7 +164,7 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
140
164
sc .psrcmut .Unlock ()
141
165
}
142
166
143
- pd , err := sc .deducer .deduceRootPath (ctx , normalizedName )
167
+ pd , err := sc .deducer .deduceRootPath (ctx , foldedNormalName )
144
168
if err != nil {
145
169
// As in the deducer, don't cache errors so that externally-driven retry
146
170
// strategies can be constructed.
@@ -189,15 +213,15 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
189
213
defer sc .srcmut .Unlock ()
190
214
// Record the name -> URL mapping, making sure that we also get the
191
215
// self-mapping.
192
- sc .nameToURL [normalizedName ] = url
193
- if url != normalizedName {
216
+ sc .nameToURL [foldedNormalName ] = url
217
+ if url != foldedNormalName {
194
218
sc .nameToURL [url ] = url
195
219
}
196
220
197
221
// Make sure we have both the folded and unfolded names recorded in the map,
198
222
// should they differ.
199
223
if normalizedName != foldedNormalName {
200
- sc .nameToURL [foldedNormalName ] = url
224
+ sc .nameToURL [normalizedName ] = url
201
225
}
202
226
203
227
if sa , has := sc .srcs [url ]; has {
0 commit comments