diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 51de9a1bc9..4f19efab26 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -64,8 +64,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.stream.Collectors; import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -101,6 +103,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @Autowired private CacheManager cacheManager; + private final Lock findOrCreateLock = new ReentrantLock(); + @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") @Override public DeviceProfile findDeviceProfileById(TenantId tenantId, DeviceProfileId deviceProfileId) { @@ -220,7 +224,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D log.trace("Executing findOrCreateDefaultDeviceProfile"); DeviceProfile deviceProfile = findDeviceProfileByName(tenantId, name); if (deviceProfile == null) { - deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default")); + try { + findOrCreateLock.lock(); + deviceProfile = findDeviceProfileByName(tenantId, name); + if (deviceProfile == null) { + deviceProfile = this.doCreateDefaultDeviceProfile(tenantId, name, name.equals("default")); + } + } finally { + findOrCreateLock.unlock(); + } } return deviceProfile; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index 132e016f5f..87e307560c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -15,6 +15,10 @@ */ package org.thingsboard.server.dao.service; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -34,6 +38,9 @@ import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -112,6 +119,23 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertNotNull(foundDefaultDeviceProfileInfo.getType()); } + @Test + public void testFindOrCreateDeviceProfile() throws ExecutionException, InterruptedException { + ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100)); + try { + List> futures = new ArrayList<>(); + for (int i = 0; i < 50; i ++) { + futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1"))); + futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2"))); + } + + List deviceProfiles = Futures.allAsList(futures).get(); + deviceProfiles.forEach(Assert::assertNotNull); + } finally { + testExecutor.shutdownNow(); + } + } + @Test public void testSetDefaultDeviceProfile() { DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1");