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 })