@@ -38,7 +38,12 @@ type AmazonECSVolumePlugin struct {
3838 volumeDrivers map [string ]driver.VolumeDriver
3939 volumes map [string ]* types.Volume
4040 state * StateManager
41- lock sync.RWMutex
41+ // mapLock protects the volumes map for short lookups and metadata-only mutations.
42+ // It must NOT be held during I/O operations (mount/unmount syscalls).
43+ mapLock sync.RWMutex
44+ // volLocks provides per-volume mutexes so that mount/unmount I/O for
45+ // different volumes can proceed concurrently.
46+ volLocks map [string ]* sync.Mutex
4247}
4348
4449// NewAmazonECSVolumePlugin initiates the volume drivers
@@ -48,16 +53,28 @@ func NewAmazonECSVolumePlugin() *AmazonECSVolumePlugin {
4853 "efs" : NewECSVolumeDriver (),
4954 "s3files" : NewECSVolumeDriver (),
5055 },
51- volumes : make (map [string ]* types.Volume ),
52- state : NewStateManager (),
56+ volumes : make (map [string ]* types.Volume ),
57+ state : NewStateManager (),
58+ volLocks : make (map [string ]* sync.Mutex ),
5359 }
5460 return plugin
5561}
5662
63+ // getOrCreateVolLock returns the per-volume mutex, creating one if it doesn't exist.
64+ // Caller must hold mapLock (at least RLock for read, Lock for create).
65+ func (a * AmazonECSVolumePlugin ) getOrCreateVolLock (name string ) * sync.Mutex {
66+ if mu , ok := a .volLocks [name ]; ok {
67+ return mu
68+ }
69+ mu := & sync.Mutex {}
70+ a .volLocks [name ] = mu
71+ return mu
72+ }
73+
5774// LoadState loads past state information of the plugin
5875func (a * AmazonECSVolumePlugin ) LoadState () error {
59- a .lock .Lock ()
60- defer a .lock .Unlock ()
76+ a .mapLock .Lock ()
77+ defer a .mapLock .Unlock ()
6178 seelog .Info ("Loading plugin state information" )
6279 oldState := & VolumeState {}
6380 if ! fileExists (PluginStateFileAbsPath ) {
@@ -96,6 +113,7 @@ func (a *AmazonECSVolumePlugin) LoadState() error {
96113 Mounts : vol .Mounts ,
97114 }
98115 a .volumes [volName ] = volume
116+ a .getOrCreateVolLock (volName )
99117 voldriver .Setup (volName , volume )
100118 }
101119 a .state .VolState = oldState
@@ -112,10 +130,11 @@ func (a *AmazonECSVolumePlugin) getVolumeDriver(driverType string) (driver.Volum
112130 return a .volumeDrivers [driverType ], nil
113131}
114132
115- // Create implements Docker volume plugin's Create Method
133+ // Create implements Docker volume plugin's Create Method.
134+ // Create is metadata-only (no I/O), so the global lock is acceptable here.
116135func (a * AmazonECSVolumePlugin ) Create (r * volume.CreateRequest ) error {
117- a .lock .Lock ()
118- defer a .lock .Unlock ()
136+ a .mapLock .Lock ()
137+ defer a .mapLock .Unlock ()
119138
120139 seelog .Infof ("Creating new volume %s" , r .Name )
121140 _ , ok := a .volumes [r .Name ]
@@ -162,6 +181,7 @@ func (a *AmazonECSVolumePlugin) Create(r *volume.CreateRequest) error {
162181 }
163182 // record the volume information
164183 a .volumes [r .Name ] = vol
184+ a .getOrCreateVolLock (r .Name )
165185 seelog .Infof ("Saving state of new volume %s" , r .Name )
166186 // save the state of new volume
167187 err = a .state .recordVolume (r .Name , vol )
@@ -198,7 +218,8 @@ func deleteMountPath(path string) error {
198218 return os .Remove (path )
199219}
200220
201- // Mount implements Docker volume plugin's Mount Method
221+ // Mount implements Docker volume plugin's Mount Method.
222+ // Uses per-volume locking so that mounts for different volumes proceed concurrently.
202223func (a * AmazonECSVolumePlugin ) Mount (r * volume.MountRequest ) (* volume.MountResponse , error ) {
203224 seelog .Infof ("Received mount request %+v" , r )
204225
@@ -210,27 +231,30 @@ func (a *AmazonECSVolumePlugin) Mount(r *volume.MountRequest) (*volume.MountResp
210231 return nil , fmt .Errorf ("no mount ID in the request" )
211232 }
212233
213- // Acquire write lock
214- a .lock .Lock ()
215- defer a .lock .Unlock ()
216-
217- // Find the volume
234+ // Phase 1: short global lock to look up volume metadata and per-volume lock
235+ a .mapLock .RLock ()
218236 vol , ok := a .volumes [r .Name ]
219237 if ! ok {
238+ a .mapLock .RUnlock ()
220239 seelog .Errorf ("Volume %s to mount is not found" , r .Name )
221240 return nil , fmt .Errorf ("volume %s not found" , r .Name )
222241 }
223-
224- // Find the volume driver
225242 volDriver , err := a .getVolumeDriver (vol .Type )
226243 if err != nil {
244+ a .mapLock .RUnlock ()
227245 seelog .Errorf ("Volume %s's driver type %s not supported: %v" , r .Name , vol .Type , err )
228246 return nil , fmt .Errorf ("Volume %s's driver type %s not supported: %w" , r .Name , vol .Type , err )
229247 }
230248 if volDriver == nil {
231- // This case shouldn't happen normally
249+ a . mapLock . RUnlock ()
232250 return nil , fmt .Errorf ("no volume driver found for type %s" , vol .Type )
233251 }
252+ volMu := a .getOrCreateVolLock (r .Name )
253+ a .mapLock .RUnlock ()
254+
255+ // Phase 2: per-volume lock — only this volume is blocked, others proceed freely
256+ volMu .Lock ()
257+ defer volMu .Unlock ()
234258
235259 // Mount the volume on the host if there are no active mounts for the volume.
236260 if len (vol .Mounts ) == 0 {
@@ -264,7 +288,8 @@ func (a *AmazonECSVolumePlugin) Mount(r *volume.MountRequest) (*volume.MountResp
264288 return & volume.MountResponse {Mountpoint : vol .Path }, nil
265289}
266290
267- // Unmount implements Docker volume plugin's Unmount Method
291+ // Unmount implements Docker volume plugin's Unmount Method.
292+ // Uses per-volume locking so that unmounts for different volumes proceed concurrently.
268293func (a * AmazonECSVolumePlugin ) Unmount (r * volume.UnmountRequest ) error {
269294 seelog .Infof ("Received unmount request %+v" , r )
270295
@@ -276,27 +301,30 @@ func (a *AmazonECSVolumePlugin) Unmount(r *volume.UnmountRequest) error {
276301 return fmt .Errorf ("no mount ID in the request" )
277302 }
278303
279- // Acquire write lock
280- a .lock .Lock ()
281- defer a .lock .Unlock ()
282-
283- // Find the volume
304+ // Phase 1: short global lock to look up volume metadata and per-volume lock
305+ a .mapLock .RLock ()
284306 vol , ok := a .volumes [r .Name ]
285307 if ! ok {
308+ a .mapLock .RUnlock ()
286309 seelog .Errorf ("Volume %s to unmount is not found" , r .Name )
287310 return fmt .Errorf ("volume %s not found" , r .Name )
288311 }
289-
290- // Get the corresponding volume driver
291312 volDriver , err := a .getVolumeDriver (vol .Type )
292313 if err != nil {
314+ a .mapLock .RUnlock ()
293315 seelog .Errorf ("Volume %s removal failure: %v" , r .Name , err )
294316 return fmt .Errorf ("volume %v of type %s is unsupported: %w" , r .Name , vol .Type , err )
295317 }
296318 if volDriver == nil {
297- // this case should not happen normally
319+ a . mapLock . RUnlock ()
298320 return fmt .Errorf ("no corresponding volume driver found for type %s" , vol .Type )
299321 }
322+ volMu := a .getOrCreateVolLock (r .Name )
323+ a .mapLock .RUnlock ()
324+
325+ // Phase 2: per-volume lock
326+ volMu .Lock ()
327+ defer volMu .Unlock ()
300328
301329 // Remove the mount from the volume
302330 seelog .Infof ("Removing mount %s from volume %s" , r .ID , r .Name )
@@ -324,12 +352,13 @@ func (a *AmazonECSVolumePlugin) Unmount(r *volume.UnmountRequest) error {
324352 return nil
325353}
326354
327- // Remove implements Docker volume plugin's Remove Method
355+ // Remove implements Docker volume plugin's Remove Method.
356+ // Uses global write lock since it mutates the volumes map.
328357func (a * AmazonECSVolumePlugin ) Remove (r * volume.RemoveRequest ) error {
329358 seelog .Infof ("Received Remove request %+v" , r )
330359
331- a .lock .Lock ()
332- defer a .lock .Unlock ()
360+ a .mapLock .Lock ()
361+ defer a .mapLock .Unlock ()
333362
334363 seelog .Infof ("Removing volume %s" , r .Name )
335364 vol , ok := a .volumes [r .Name ]
@@ -362,6 +391,7 @@ func (a *AmazonECSVolumePlugin) Remove(r *volume.RemoveRequest) error {
362391
363392 // remove the volume information
364393 delete (a .volumes , r .Name )
394+ delete (a .volLocks , r .Name )
365395 // cleanup the volume's host mount path
366396 err = a .CleanupMountPath (vol .Path )
367397 if err != nil {
@@ -378,8 +408,8 @@ func (a *AmazonECSVolumePlugin) Remove(r *volume.RemoveRequest) error {
378408
379409// List implements Docker volume plugin's List Method
380410func (a * AmazonECSVolumePlugin ) List () (* volume.ListResponse , error ) {
381- a .lock .RLock ()
382- defer a .lock .RUnlock ()
411+ a .mapLock .RLock ()
412+ defer a .mapLock .RUnlock ()
383413 vols := make ([]* volume.Volume , len (a .volumes ))
384414 i := 0
385415 for volName := range a .volumes {
@@ -396,8 +426,8 @@ func (a *AmazonECSVolumePlugin) List() (*volume.ListResponse, error) {
396426
397427// Get implements Docker volume plugin's Get Method
398428func (a * AmazonECSVolumePlugin ) Get (r * volume.GetRequest ) (* volume.GetResponse , error ) {
399- a .lock .RLock ()
400- defer a .lock .RUnlock ()
429+ a .mapLock .RLock ()
430+ defer a .mapLock .RUnlock ()
401431 vol , ok := a .volumes [r .Name ]
402432 if ! ok {
403433 return nil , fmt .Errorf ("volume %s not found" , r .Name )
@@ -413,8 +443,8 @@ func (a *AmazonECSVolumePlugin) Get(r *volume.GetRequest) (*volume.GetResponse,
413443
414444// Path implements Docker volume plugin's Path Method
415445func (a * AmazonECSVolumePlugin ) Path (r * volume.PathRequest ) (* volume.PathResponse , error ) {
416- a .lock .RLock ()
417- defer a .lock .RUnlock ()
446+ a .mapLock .RLock ()
447+ defer a .mapLock .RUnlock ()
418448 vol , ok := a .volumes [r .Name ]
419449 if ! ok {
420450 seelog .Errorf ("Could not find mount path for volume %s" , r .Name )
0 commit comments