github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/decomposedfs_concurrency_test.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package decomposedfs_test
    20  
    21  import (
    22  	"os"
    23  	"path"
    24  	"sync"
    25  
    26  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    27  	testhelpers "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/testhelpers"
    28  	"github.com/rogpeppe/go-internal/lockedfile"
    29  	"github.com/stretchr/testify/mock"
    30  
    31  	"github.com/cs3org/reva/v2/tests/helpers"
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  )
    35  
    36  var _ = Describe("Decomposed", func() {
    37  	var (
    38  		env *testhelpers.TestEnv
    39  	)
    40  
    41  	BeforeEach(func() {
    42  		var err error
    43  		env, err = testhelpers.NewTestEnv(nil)
    44  		Expect(err).ToNot(HaveOccurred())
    45  	})
    46  
    47  	AfterEach(func() {
    48  		if env != nil {
    49  			os.RemoveAll(env.Root)
    50  		}
    51  	})
    52  
    53  	Describe("file locking", func() {
    54  		Describe("lockedfile", func() {
    55  			It("allows shared locks while shared locks are being held", func() {
    56  				states := sync.Map{}
    57  
    58  				path, err := os.CreateTemp("", "decomposedfs-lockedfile-test-")
    59  				Expect(err).ToNot(HaveOccurred())
    60  				states.Store("managedToOpenroFile", false)
    61  				roFile, err := lockedfile.OpenFile(path.Name(), os.O_RDONLY, 0)
    62  				Expect(err).ToNot(HaveOccurred())
    63  				defer roFile.Close()
    64  
    65  				go func() {
    66  					roFile2, err := lockedfile.OpenFile(path.Name(), os.O_RDONLY, 0)
    67  					Expect(err).ToNot(HaveOccurred())
    68  					defer roFile2.Close()
    69  					states.Store("managedToOpenroFile", true)
    70  				}()
    71  				Eventually(func() bool { s, _ := states.Load("managedToOpenroFile"); return s.(bool) }).Should(BeTrue())
    72  			})
    73  
    74  			It("prevents exclusive locks while shared locks are being held", func() {
    75  				states := sync.Map{}
    76  
    77  				path, err := os.CreateTemp("", "decomposedfs-lockedfile-test-")
    78  				Expect(err).ToNot(HaveOccurred())
    79  				states.Store("managedToOpenwoFile", false)
    80  				roFile, err := lockedfile.OpenFile(path.Name(), os.O_RDONLY, 0)
    81  				Expect(err).ToNot(HaveOccurred())
    82  
    83  				go func() {
    84  					woFile, err := lockedfile.OpenFile(path.Name(), os.O_WRONLY, 0)
    85  					Expect(err).ToNot(HaveOccurred())
    86  					states.Store("managedToOpenwoFile", true)
    87  					woFile.Close()
    88  				}()
    89  				Consistently(func() bool { s, _ := states.Load("managedToOpenwoFile"); return s.(bool) }).Should(BeFalse())
    90  
    91  				roFile.Close()
    92  				Eventually(func() bool { s, _ := states.Load("managedToOpenwoFile"); return s.(bool) }).Should(BeTrue())
    93  			})
    94  
    95  			It("prevents shared locks while an exclusive lock is being held", func() {
    96  				states := sync.Map{}
    97  
    98  				path, err := os.CreateTemp("", "decomposedfs-lockedfile-test-")
    99  				Expect(err).ToNot(HaveOccurred())
   100  				states.Store("managedToOpenroFile", false)
   101  				woFile, err := lockedfile.OpenFile(path.Name(), os.O_WRONLY, 0)
   102  				Expect(err).ToNot(HaveOccurred())
   103  				defer woFile.Close()
   104  				go func() {
   105  					roFile, err := lockedfile.OpenFile(path.Name(), os.O_RDONLY, 0)
   106  					Expect(err).ToNot(HaveOccurred())
   107  					defer roFile.Close()
   108  					states.Store("managedToOpenroFile", true)
   109  				}()
   110  				Consistently(func() bool { s, _ := states.Load("managedToOpenroFile"); return s.(bool) }).Should(BeFalse())
   111  
   112  				woFile.Close()
   113  				Eventually(func() bool { s, _ := states.Load("managedToOpenroFile"); return s.(bool) }).Should(BeTrue())
   114  			})
   115  
   116  			It("allows opening rw while an exclusive lock is being held", func() {
   117  				states := sync.Map{}
   118  
   119  				path, err := os.CreateTemp("", "decomposedfs-lockedfile-test-")
   120  				Expect(err).ToNot(HaveOccurred())
   121  				states.Store("managedToOpenrwLockedFile", false)
   122  				woFile, err := lockedfile.OpenFile(path.Name(), os.O_WRONLY, 0)
   123  				Expect(err).ToNot(HaveOccurred())
   124  				defer woFile.Close()
   125  				go func() {
   126  					roFile, err := os.OpenFile(path.Name(), os.O_RDWR, 0)
   127  					Expect(err).ToNot(HaveOccurred())
   128  					defer roFile.Close()
   129  					states.Store("managedToOpenrwLockedFile", true)
   130  				}()
   131  
   132  				woFile.Close()
   133  				Eventually(func() bool { s, _ := states.Load("managedToOpenrwLockedFile"); return s.(bool) }).Should(BeTrue())
   134  			})
   135  		})
   136  
   137  	})
   138  
   139  	Describe("concurrent", func() {
   140  		Describe("Upload", func() {
   141  			var (
   142  				r1 = []byte("test")
   143  				r2 = []byte("another run")
   144  			)
   145  
   146  			PIt("generates two revisions", func() {
   147  				// runtime.GOMAXPROCS(1) // uncomment to remove concurrency and see revisions working.
   148  				wg := &sync.WaitGroup{}
   149  				wg.Add(2)
   150  
   151  				// upload file with contents: "test"
   152  				go func(wg *sync.WaitGroup) {
   153  					_ = helpers.Upload(env.Ctx, env.Fs, &provider.Reference{Path: "uploaded.txt"}, r1)
   154  					wg.Done()
   155  				}(wg)
   156  
   157  				// upload file with contents: "another run"
   158  				go func(wg *sync.WaitGroup) {
   159  					_ = helpers.Upload(env.Ctx, env.Fs, &provider.Reference{Path: "uploaded.txt"}, r2)
   160  					wg.Done()
   161  				}(wg)
   162  
   163  				// this test, by the way the oCIS storage is implemented, is non-deterministic, and the contents
   164  				// of uploaded.txt will change on each run depending on which of the 2 routines above makes it
   165  				// first into the scheduler. In order to make it deterministic, we have to consider the Upload impl-
   166  				// ementation and we can leverage concurrency and add locks only when the destination path are the
   167  				// same for 2 uploads.
   168  
   169  				wg.Wait()
   170  				revisions, err := env.Fs.ListRevisions(env.Ctx, &provider.Reference{Path: "uploaded.txt"})
   171  				Expect(err).ToNot(HaveOccurred())
   172  				Expect(len(revisions)).To(Equal(1))
   173  
   174  				_, err = os.ReadFile(path.Join(env.Root, "nodes", "root", "uploaded.txt"))
   175  				Expect(err).ToNot(HaveOccurred())
   176  			})
   177  		})
   178  
   179  		Describe("CreateDir", func() {
   180  			JustBeforeEach(func() {
   181  				env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{
   182  					Stat:            true,
   183  					CreateContainer: true,
   184  				}, nil)
   185  			})
   186  			It("handles already existing directories", func() {
   187  				var numIterations = 10
   188  				wg := &sync.WaitGroup{}
   189  				wg.Add(numIterations)
   190  				for i := 0; i < numIterations; i++ {
   191  					go func(wg *sync.WaitGroup) {
   192  						defer GinkgoRecover()
   193  						defer wg.Done()
   194  						ref := &provider.Reference{
   195  							ResourceId: env.SpaceRootRes,
   196  							Path:       "./fightforit",
   197  						}
   198  						if err := env.Fs.CreateDir(env.Ctx, ref); err != nil {
   199  							Expect(err).To(MatchError(ContainSubstring("already exists")))
   200  							rinfo, err := env.Fs.GetMD(env.Ctx, ref, nil, nil)
   201  							Expect(err).ToNot(HaveOccurred())
   202  							Expect(rinfo).ToNot(BeNil())
   203  						}
   204  					}(wg)
   205  				}
   206  				wg.Wait()
   207  			})
   208  		})
   209  	})
   210  })