Files
ArchiSteamFarm/ArchiSteamFarm/InMemoryServerListProvider.cs

54 lines
2.3 KiB
C#
Raw Normal View History

2017-11-18 17:27:06 +01:00
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
2018-01-01 02:56:53 +01:00
// Copyright 2015-2018 Łukasz "JustArchi" Domeradzki
2017-11-18 17:27:06 +01:00
// Contact: JustArchi@JustArchi.net
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections.Generic;
2017-07-21 14:25:25 +02:00
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SteamKit2.Discovery;
namespace ArchiSteamFarm {
Fix async/await with ConcurrentHashSet Now this is a nice bug that was found accidentally by ArchiBoT... ReaderWriterLockSlim() is very decent solution, but it's thread-based, and we're using our ConcurrentHashSet in mixed async/sync context. This means that if we use something like: foreach (var item in concHashSet) { await AnythingAsync().ConfigureAwait(false); } It's totally possible that we'll request read lock as thread 1, and release the read lock as thread 2, which will lead to RWLock exception => System.Threading.SynchronizationLockException: The read lock is being released without being held. Fortunately it looks like we didn't have any scenario like this in ASF, as this was possible only when we async/await while enumerating over ConcurrentHashSet, so that specific bug didn't affect ASF codebase (yet). Still, I must fix this as current implementation is not thread-safe, so our HashSet is in fact not concurrent in the first place. I analyzed possible solutions and there are basically 3: either using ConcurrentDictionary and wrapping around it, replacing lock with SemaphoreSlim, or using third-party AsyncReaderWriterLock from StephenCleary. SemaphoreSlim entirely kills the concept of multiple readers one writer, and could affect performance negatively, moreover - it doesn't support upgreadable lock scenario we have with ReplaceIfNeededWith(). Concurrent dictionary would be nice if I didn't have that awful memory hit from storing mandatory pointless value, plus I don't really like concept of wrapping around conc dictionary if I can simply use it right away and drop my conc hashset entirely. AsyncReaderWriterLock seem to be really well written, and works on Mono + should be compatible with .NET core in the future, so we should go for it as it's the best bet both performance-wise and memory-wise. This brings another package dependency and changes a bit backend of ConcurrentHashSet
2017-02-07 20:14:51 +01:00
internal sealed class InMemoryServerListProvider : IServerListProvider {
[JsonProperty(Required = Required.DisallowNull)]
2017-07-21 14:25:25 +02:00
private readonly ConcurrentHashSet<ServerRecordEndPoint> ServerRecords = new ConcurrentHashSet<ServerRecordEndPoint>();
2017-07-21 14:25:25 +02:00
public Task<IEnumerable<ServerRecord>> FetchServerListAsync() => Task.FromResult(ServerRecords.Select(server => ServerRecord.CreateServer(server.Host, server.Port, server.ProtocolTypes)));
public Task UpdateServerListAsync(IEnumerable<ServerRecord> endpoints) {
if (endpoints == null) {
ASF.ArchiLogger.LogNullError(nameof(endpoints));
2017-03-03 13:25:34 +01:00
return Task.CompletedTask;
}
2017-07-21 14:25:25 +02:00
HashSet<ServerRecordEndPoint> newServerRecords = new HashSet<ServerRecordEndPoint>(endpoints.Select(ep => new ServerRecordEndPoint(ep.GetHost(), (ushort) ep.GetPort(), ep.ProtocolTypes)));
2017-07-21 14:25:25 +02:00
if (!ServerRecords.ReplaceIfNeededWith(newServerRecords)) {
2017-03-03 13:25:34 +01:00
return Task.CompletedTask;
}
ServerListUpdated?.Invoke(this, EventArgs.Empty);
2017-03-03 13:25:34 +01:00
return Task.CompletedTask;
}
2016-08-19 05:25:08 +02:00
internal event EventHandler ServerListUpdated;
}
}