github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/puller/sorter/backend_pool_test.go (about) 1 // Copyright 2020 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package sorter 15 16 import ( 17 "context" 18 "fmt" 19 "os" 20 "path/filepath" 21 "strconv" 22 "time" 23 24 "github.com/pingcap/check" 25 "github.com/pingcap/failpoint" 26 "github.com/pingcap/ticdc/pkg/config" 27 "github.com/pingcap/ticdc/pkg/filelock" 28 "github.com/pingcap/ticdc/pkg/util/testleak" 29 ) 30 31 type backendPoolSuite struct{} 32 33 var _ = check.SerialSuites(&backendPoolSuite{}) 34 35 func (s *backendPoolSuite) TestBasicFunction(c *check.C) { 36 defer testleak.AfterTest(c)() 37 38 dataDir := c.MkDir() 39 err := os.MkdirAll(dataDir, 0o755) 40 c.Assert(err, check.IsNil) 41 42 sortDir := filepath.Join(dataDir, config.DefaultSortDir) 43 err = os.MkdirAll(sortDir, 0o755) 44 c.Assert(err, check.IsNil) 45 46 conf := config.GetDefaultServerConfig() 47 conf.DataDir = dataDir 48 conf.Sorter.SortDir = sortDir 49 conf.Sorter.MaxMemoryPressure = 90 // 90% 50 conf.Sorter.MaxMemoryConsumption = 16 * 1024 * 1024 * 1024 // 16G 51 config.StoreGlobalServerConfig(conf) 52 53 err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(100)") 54 c.Assert(err, check.IsNil) 55 56 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 57 defer cancel() 58 59 backEndPool, err := newBackEndPool(sortDir, "") 60 c.Assert(err, check.IsNil) 61 c.Assert(backEndPool, check.NotNil) 62 defer backEndPool.terminate() 63 64 backEnd, err := backEndPool.alloc(ctx) 65 c.Assert(err, check.IsNil) 66 c.Assert(backEnd, check.FitsTypeOf, &fileBackEnd{}) 67 fileName := backEnd.(*fileBackEnd).fileName 68 c.Assert(fileName, check.Not(check.Equals), "") 69 70 err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(0)") 71 c.Assert(err, check.IsNil) 72 err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryUsageInjectPoint", "return(34359738368)") 73 c.Assert(err, check.IsNil) 74 75 backEnd1, err := backEndPool.alloc(ctx) 76 c.Assert(err, check.IsNil) 77 c.Assert(backEnd1, check.FitsTypeOf, &fileBackEnd{}) 78 fileName1 := backEnd1.(*fileBackEnd).fileName 79 c.Assert(fileName1, check.Not(check.Equals), "") 80 c.Assert(fileName1, check.Not(check.Equals), fileName) 81 82 err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(0)") 83 c.Assert(err, check.IsNil) 84 err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryUsageInjectPoint", "return(0)") 85 c.Assert(err, check.IsNil) 86 87 backEnd2, err := backEndPool.alloc(ctx) 88 c.Assert(err, check.IsNil) 89 c.Assert(backEnd2, check.FitsTypeOf, &memoryBackEnd{}) 90 91 err = backEndPool.dealloc(backEnd) 92 c.Assert(err, check.IsNil) 93 94 err = backEndPool.dealloc(backEnd1) 95 c.Assert(err, check.IsNil) 96 97 err = backEndPool.dealloc(backEnd2) 98 c.Assert(err, check.IsNil) 99 100 time.Sleep(backgroundJobInterval * 3 / 2) 101 102 _, err = os.Stat(fileName) 103 c.Assert(os.IsNotExist(err), check.IsTrue) 104 105 _, err = os.Stat(fileName1) 106 c.Assert(os.IsNotExist(err), check.IsTrue) 107 } 108 109 // TestDirectoryBadPermission verifies that no permission to ls the directory does not prevent using it 110 // as a temporary file directory. 111 func (s *backendPoolSuite) TestDirectoryBadPermission(c *check.C) { 112 defer testleak.AfterTest(c)() 113 114 dataDir := c.MkDir() 115 sortDir := filepath.Join(dataDir, config.DefaultSortDir) 116 err := os.MkdirAll(sortDir, 0o755) 117 c.Assert(err, check.IsNil) 118 119 err = os.Chmod(sortDir, 0o311) // no permission to `ls` 120 c.Assert(err, check.IsNil) 121 122 conf := config.GetGlobalServerConfig() 123 conf.DataDir = dataDir 124 conf.Sorter.SortDir = sortDir 125 conf.Sorter.MaxMemoryPressure = 0 // force using files 126 127 backEndPool, err := newBackEndPool(sortDir, "") 128 c.Assert(err, check.IsNil) 129 c.Assert(backEndPool, check.NotNil) 130 defer backEndPool.terminate() 131 132 backEnd, err := backEndPool.alloc(context.Background()) 133 c.Assert(err, check.IsNil) 134 defer backEnd.free() //nolint:errcheck 135 136 fileName := backEnd.(*fileBackEnd).fileName 137 _, err = os.Stat(fileName) 138 c.Assert(err, check.IsNil) // assert that the file exists 139 140 err = backEndPool.dealloc(backEnd) 141 c.Assert(err, check.IsNil) 142 } 143 144 // TestCleanUpSelf verifies that the backendPool correctly cleans up files used by itself on exit. 145 func (s *backendPoolSuite) TestCleanUpSelf(c *check.C) { 146 defer testleak.AfterTest(c)() 147 148 dataDir := c.MkDir() 149 err := os.Chmod(dataDir, 0o755) 150 c.Assert(err, check.IsNil) 151 152 sorterDir := filepath.Join(dataDir, config.DefaultSortDir) 153 err = os.MkdirAll(sorterDir, 0o755) 154 c.Assert(err, check.IsNil) 155 156 conf := config.GetDefaultServerConfig() 157 conf.DataDir = dataDir 158 conf.Sorter.SortDir = sorterDir 159 conf.Sorter.MaxMemoryPressure = 90 // 90% 160 conf.Sorter.MaxMemoryConsumption = 16 * 1024 * 1024 * 1024 // 16G 161 config.StoreGlobalServerConfig(conf) 162 163 err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(100)") 164 c.Assert(err, check.IsNil) 165 defer failpoint.Disable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint") //nolint:errcheck 166 167 backEndPool, err := newBackEndPool(sorterDir, "") 168 c.Assert(err, check.IsNil) 169 c.Assert(backEndPool, check.NotNil) 170 171 ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) 172 defer cancel() 173 174 var fileNames []string 175 for i := 0; i < 20; i++ { 176 backEnd, err := backEndPool.alloc(ctx) 177 c.Assert(err, check.IsNil) 178 c.Assert(backEnd, check.FitsTypeOf, &fileBackEnd{}) 179 180 fileName := backEnd.(*fileBackEnd).fileName 181 _, err = os.Stat(fileName) 182 c.Assert(err, check.IsNil) 183 184 fileNames = append(fileNames, fileName) 185 } 186 187 prefix := backEndPool.filePrefix 188 c.Assert(prefix, check.Not(check.Equals), "") 189 190 for j := 100; j < 120; j++ { 191 fileName := prefix + strconv.Itoa(j) + ".tmp" 192 f, err := os.Create(fileName) 193 c.Assert(err, check.IsNil) 194 err = f.Close() 195 c.Assert(err, check.IsNil) 196 197 fileNames = append(fileNames, fileName) 198 } 199 200 backEndPool.terminate() 201 202 for _, fileName := range fileNames { 203 _, err = os.Stat(fileName) 204 c.Assert(os.IsNotExist(err), check.IsTrue) 205 } 206 } 207 208 type mockOtherProcess struct { 209 dir string 210 prefix string 211 flock *filelock.FileLock 212 files []string 213 } 214 215 func newMockOtherProcess(c *check.C, dir string, prefix string) *mockOtherProcess { 216 prefixLockPath := fmt.Sprintf("%s/%s", dir, sortDirLockFileName) 217 flock, err := filelock.NewFileLock(prefixLockPath) 218 c.Assert(err, check.IsNil) 219 220 err = flock.Lock() 221 c.Assert(err, check.IsNil) 222 223 return &mockOtherProcess{ 224 dir: dir, 225 prefix: prefix, 226 flock: flock, 227 } 228 } 229 230 func (p *mockOtherProcess) writeMockFiles(c *check.C, num int) { 231 for i := 0; i < num; i++ { 232 fileName := fmt.Sprintf("%s%d", p.prefix, i) 233 f, err := os.Create(fileName) 234 c.Assert(err, check.IsNil) 235 _ = f.Close() 236 p.files = append(p.files, fileName) 237 } 238 } 239 240 func (p *mockOtherProcess) changeLockPermission(c *check.C, mode os.FileMode) { 241 prefixLockPath := fmt.Sprintf("%s/%s", p.dir, sortDirLockFileName) 242 err := os.Chmod(prefixLockPath, mode) 243 c.Assert(err, check.IsNil) 244 } 245 246 func (p *mockOtherProcess) unlock(c *check.C) { 247 err := p.flock.Unlock() 248 c.Assert(err, check.IsNil) 249 } 250 251 func (p *mockOtherProcess) assertFilesExist(c *check.C) { 252 for _, file := range p.files { 253 _, err := os.Stat(file) 254 c.Assert(err, check.IsNil) 255 } 256 } 257 258 func (p *mockOtherProcess) assertFilesNotExist(c *check.C) { 259 for _, file := range p.files { 260 _, err := os.Stat(file) 261 c.Assert(os.IsNotExist(err), check.IsTrue) 262 } 263 } 264 265 // TestCleanUpStaleBasic verifies that the backendPool correctly cleans up stale temporary files 266 // left by other CDC processes that have exited abnormally. 267 func (s *backendPoolSuite) TestCleanUpStaleBasic(c *check.C) { 268 defer testleak.AfterTest(c)() 269 270 dir := c.MkDir() 271 prefix := dir + "/sort-1-" 272 273 mockP := newMockOtherProcess(c, dir, prefix) 274 mockP.writeMockFiles(c, 100) 275 mockP.unlock(c) 276 mockP.assertFilesExist(c) 277 278 backEndPool, err := newBackEndPool(dir, "") 279 c.Assert(err, check.IsNil) 280 c.Assert(backEndPool, check.NotNil) 281 defer backEndPool.terminate() 282 283 mockP.assertFilesNotExist(c) 284 } 285 286 // TestFileLockConflict tests that if two backEndPools were to use the same sort-dir, 287 // and error would be returned by one of them. 288 func (s *backendPoolSuite) TestFileLockConflict(c *check.C) { 289 defer testleak.AfterTest(c)() 290 dir := c.MkDir() 291 292 backEndPool1, err := newBackEndPool(dir, "") 293 c.Assert(err, check.IsNil) 294 c.Assert(backEndPool1, check.NotNil) 295 defer backEndPool1.terminate() 296 297 backEndPool2, err := newBackEndPool(dir, "") 298 c.Assert(err, check.ErrorMatches, ".*file lock conflict.*") 299 c.Assert(backEndPool2, check.IsNil) 300 } 301 302 // TestCleanUpStaleBasic verifies that the backendPool correctly cleans up stale temporary files 303 // left by other CDC processes that have exited abnormally. 304 func (s *backendPoolSuite) TestCleanUpStaleLockNoPermission(c *check.C) { 305 defer testleak.AfterTest(c)() 306 307 dir := c.MkDir() 308 prefix := dir + "/sort-1-" 309 310 mockP := newMockOtherProcess(c, dir, prefix) 311 mockP.writeMockFiles(c, 100) 312 // set a bad permission 313 mockP.changeLockPermission(c, 0o000) 314 315 backEndPool, err := newBackEndPool(dir, "") 316 c.Assert(err, check.ErrorMatches, ".*permission denied.*") 317 c.Assert(backEndPool, check.IsNil) 318 319 mockP.assertFilesExist(c) 320 } 321 322 // TestGetMemoryPressureFailure verifies that the backendPool can handle gracefully failures that happen when 323 // getting the current system memory pressure. Such a failure is usually caused by a lack of file descriptor quota 324 // set by the operating system. 325 func (s *backendPoolSuite) TestGetMemoryPressureFailure(c *check.C) { 326 defer testleak.AfterTest(c)() 327 328 err := failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/getMemoryPressureFails", "return(true)") 329 c.Assert(err, check.IsNil) 330 defer failpoint.Disable("github.com/pingcap/ticdc/cdc/puller/sorter/getMemoryPressureFails") //nolint:errcheck 331 332 dir := c.MkDir() 333 backEndPool, err := newBackEndPool(dir, "") 334 c.Assert(err, check.IsNil) 335 c.Assert(backEndPool, check.NotNil) 336 defer backEndPool.terminate() 337 338 after := time.After(time.Second * 20) 339 tick := time.Tick(time.Second * 1) 340 for { 341 select { 342 case <-after: 343 c.Fatal("TestGetMemoryPressureFailure timed out") 344 case <-tick: 345 if backEndPool.memoryPressure() == 100 { 346 return 347 } 348 } 349 } 350 }