jetcache icon indicating copy to clipboard operation
jetcache copied to clipboard

CacheResult 线程安全问题

Open lx-dong opened this issue 2 years ago • 7 comments

背景:我们自己实现了 CacheMonitor 对缓存读写事件进行监听统计。压测时发现有空指针问题。 排查问题: CacheResult 的属性读写都没有做线程安全防护,正常情况下只有缓存读写的主线程会访问这个对象实例。但是有个CacheMonitor接口,CacheEvent的实现类中会透传CacheResult(例如 CacheGetEvent)。此时若CacheMonitor实现类内部另起监听线程,那么就会存在多个线程同时读写CacheResult的场景,引发线程安全问题。

lx-dong avatar Sep 27 '21 06:09 lx-dong

我们的解决方式是对 CacheResult#waitForResult 方法加synchronized..

public void waitForResult(Duration timeout) { synchronized (this) { if (resultCode != null) { return; } try { ResultData resultData = future.toCompletableFuture().get( timeout.toMillis(), TimeUnit.MILLISECONDS); fetchResultSuccess(resultData); } catch (TimeoutException | InterruptedException | ExecutionException e) { fetchResultFail(e); } } }

lx-dong avatar Sep 27 '21 06:09 lx-dong

感觉最好的解决方式应该是 CacheEvent 这个事件中传递的是一个final的只读结果,而不是一个有可能为中间态且与主线程共享的对象

lx-dong avatar Sep 28 '21 05:09 lx-dong

具体错误是什么?可能是有的地方没有加volatile

areyouok avatar Sep 28 '21 12:09 areyouok

具体错误是什么?可能是有的地方没有加volatile

  1. 第一个报错的空指针在 MultiLevelCache#do_GET -> #unwrapHolder -> Objects.requireNonNull(h);

  2. 另外一个是缓存返回null(期望值肯定是非空) 具体原因就是CacheMonitor 消费event的时间比主线程早,导致isSuccess() == true,但是value还没set完成。

根本原因是 CacheGetResult 的 resultCode, value, holder 属性读写不是原子的,并发就有问题,类似读写锁解决的问题场景。除了synchronized,我也对这几个参数加了volatile。

lx-dong avatar Sep 28 '21 13:09 lx-dong

另外 CacheResult#isSuccess,#getResultCode 等方法不仅仅是一个get方法,里面有#waitForResult这种写操作,这就比较坑了,实现CacheMonitor时一旦调用了这些方法又不知道内部实现的话肯定踩坑。

lx-dong avatar Sep 28 '21 14:09 lx-dong

我大概知道怎么回事了,过两天修一下。 这个地方可以不加synchronized,就是稍微麻烦点。

areyouok avatar Sep 29 '21 16:09 areyouok

已经修好了,你用最新的master试下?

areyouok avatar Oct 02 '21 15:10 areyouok