Skip to content

Commit 9cfac50

Browse files
authored
Merge pull request LGouellec#345 from LGouellec/issue/314
Fix Issue/314
2 parents cc2fdf4 + 52bca51 commit 9cfac50

File tree

7 files changed

+265
-10
lines changed

7 files changed

+265
-10
lines changed

core/State/InMemory/InMemoryWindowStore.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Streamiz.Kafka.Net.Metrics;
1313
using Streamiz.Kafka.Net.Metrics.Internal;
1414
using Streamiz.Kafka.Net.State.Helper;
15+
using Streamiz.Kafka.Net.State.InMemory.Internal;
1516

1617
namespace Streamiz.Kafka.Net.State.InMemory
1718
{
@@ -260,7 +261,8 @@ public Windowed<Bytes> PeekNextKey()
260261

261262
#endregion
262263

263-
internal class InMemoryWindowStore : IWindowStore<Bytes, byte[]>
264+
internal class
265+
InMemoryWindowStore : IWindowStore<Bytes, byte[]>
264266
{
265267
private readonly TimeSpan retention;
266268
private readonly long size;
@@ -272,8 +274,7 @@ internal class InMemoryWindowStore : IWindowStore<Bytes, byte[]>
272274
private int seqnum = 0;
273275

274276
private readonly ConcurrentDictionary<long, ConcurrentDictionary<Bytes, byte[]>> map = new();
275-
276-
private readonly ISet<InMemoryWindowStoreEnumeratorWrapper> openIterators = new HashSet<InMemoryWindowStoreEnumeratorWrapper>();
277+
private readonly ConcurrentSet<InMemoryWindowStoreEnumeratorWrapper> openIterators = new();
277278

278279
private readonly ILogger logger = Logger.GetLogger(typeof(InMemoryWindowStore));
279280

@@ -309,8 +310,9 @@ public void Close()
309310
if (openIterators.Count != 0)
310311
{
311312
logger.LogWarning("Closing {OpenIteratorCount} open iterators for store {Name}", openIterators.Count, Name);
312-
for (int i = 0; i< openIterators.Count; ++i)
313-
openIterators.ElementAt(i).Close();
313+
foreach(var iterator in openIterators)
314+
iterator.Close();
315+
openIterators.Clear();
314316
}
315317

316318
map.Clear();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.Collections.Concurrent;
2+
using System.Collections.Generic;
3+
4+
namespace Streamiz.Kafka.Net.State.InMemory.Internal
5+
{
6+
internal class ConcurrentSet<T>
7+
{
8+
private readonly ConcurrentDictionary<T, byte> _dictionary = new();
9+
10+
/// <summary>
11+
/// Returns an enumerator that iterates through the collection.
12+
/// </summary>
13+
/// <returns>
14+
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
15+
/// </returns>
16+
public IEnumerator<T> GetEnumerator()
17+
{
18+
return _dictionary.Keys.GetEnumerator();
19+
}
20+
21+
/// <summary>
22+
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
23+
/// </summary>
24+
/// <returns>
25+
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
26+
/// </returns>
27+
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
28+
public bool Remove(T item)
29+
{
30+
return TryRemove(item);
31+
}
32+
33+
/// <summary>
34+
/// Gets the number of elements in the set.
35+
/// </summary>
36+
public int Count => _dictionary.Count;
37+
38+
/// <summary>
39+
/// Adds an element to the current set and returns a value to indicate if the element was successfully added.
40+
/// </summary>
41+
/// <returns>
42+
/// true if the element is added to the set; false if the element is already in the set.
43+
/// </returns>
44+
/// <param name="item">The element to add to the set.</param>
45+
public bool Add(T item)
46+
{
47+
return TryAdd(item);
48+
}
49+
50+
public void Clear()
51+
{
52+
_dictionary.Clear();
53+
}
54+
55+
public bool Contains(T item)
56+
{
57+
return _dictionary.ContainsKey(item);
58+
}
59+
60+
private bool TryAdd(T item)
61+
{
62+
return _dictionary.TryAdd(item, default);
63+
}
64+
65+
private bool TryRemove(T item)
66+
{
67+
return _dictionary.TryRemove(item, out _);
68+
}
69+
}
70+
71+
}

environment/datagen_connector.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "datagen-users",
3+
"config": {
4+
"connector.class": "io.confluent.kafka.connect.datagen.DatagenConnector",
5+
"kafka.topic": "users",
6+
"quickstart": "users",
7+
"key.converter": "org.apache.kafka.connect.storage.StringConverter",
8+
"value.converter": "org.apache.kafka.connect.json.JsonConverter",
9+
"value.converter.schemas.enable": "false",
10+
"max.interval": 1000,
11+
"iterations": 10000000,
12+
"tasks.max": "1"
13+
}
14+
}

environment/docker-compose-with-connect.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
version: '2'
33
services:
44
zookeeper:
5-
image: confluentinc/cp-zookeeper:7.4.0
5+
image: confluentinc/cp-zookeeper:7.6.1
66
hostname: zookeeper
77
container_name: zookeeper
88
ports:
@@ -13,7 +13,7 @@ services:
1313
KAFKA_OPTS: "-Dzookeeper.4lw.commands.whitelist=*"
1414

1515
broker:
16-
image: confluentinc/cp-server:7.4.0
16+
image: confluentinc/cp-server:7.6.1
1717
hostname: broker
1818
container_name: broker
1919
depends_on:
@@ -42,7 +42,7 @@ services:
4242
CONFLUENT_SUPPORT_CUSTOMER_ID: 'anonymous'
4343

4444
schema-registry:
45-
image: confluentinc/cp-schema-registry:7.4.0
45+
image: confluentinc/cp-schema-registry:7.6.1
4646
hostname: schema-registry
4747
container_name: schema-registry
4848
depends_on:
@@ -55,7 +55,7 @@ services:
5555
SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081
5656

5757
connect:
58-
image: cnfldemos/kafka-connect-datagen:0.6.0-7.3.0
58+
image: cnfldemos/kafka-connect-datagen:0.6.4-7.6.0
5959
container_name: connect
6060
depends_on:
6161
- broker

samples/sample-stream/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace sample_stream
1414
{
1515
public static class Program
1616
{
17-
public static async Task Main(string[] args)
17+
public static async Task Main2(string[] args)
1818
{
1919
var config = new StreamConfig<StringSerDes, StringSerDes>{
2020
ApplicationId = $"test-app",
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Confluent.Kafka;
5+
using Streamiz.Kafka.Net;
6+
using Streamiz.Kafka.Net.SerDes;
7+
using Streamiz.Kafka.Net.State;
8+
using Streamiz.Kafka.Net.Stream;
9+
using Streamiz.Kafka.Net.Table;
10+
11+
namespace sample_stream;
12+
13+
public class Reproducer314
14+
{
15+
public static async Task Main(string[] args)
16+
{
17+
Console.WriteLine("Hello Streams");
18+
19+
var config = new StreamConfig<StringSerDes, StringSerDes>
20+
{
21+
ApplicationId = $"test-windowedtable-bis",
22+
BootstrapServers = "localhost:9092",
23+
AutoOffsetReset = AutoOffsetReset.Earliest
24+
};
25+
26+
var builder = CreateWindowedStore();
27+
var t = builder.Build();
28+
var windowedTableStream = new KafkaStream(t, config);
29+
30+
await windowedTableStream.StartAsync();
31+
32+
// wait for the store to be restored and ready
33+
Thread.Sleep(10000);
34+
35+
GetValueFromWindowedStore(windowedTableStream, DateTime.UtcNow.AddHours(-1), new CancellationToken());
36+
37+
Console.WriteLine("Finished");
38+
}
39+
40+
private static void GetValueFromWindowedStore(KafkaStream windowedTableStream, DateTime startUtcForWindowLookup, CancellationToken cancellationToken)
41+
{
42+
var windowedStore = windowedTableStream.Store(StoreQueryParameters.FromNameAndType("store", QueryableStoreTypes.WindowStore<string, int>()));
43+
44+
while (!cancellationToken.IsCancellationRequested)
45+
{
46+
var records = windowedStore.FetchAll(startUtcForWindowLookup, DateTime.UtcNow).ToList();
47+
48+
if (records.Count > 0)
49+
{
50+
foreach (var item in records)
51+
{
52+
Console.WriteLine($"Value from windowed store : KEY = {item.Key} VALUE = {item.Value}");
53+
}
54+
55+
startUtcForWindowLookup = DateTime.UtcNow;
56+
}
57+
}
58+
}
59+
60+
private static StreamBuilder CreateWindowedStore()
61+
{
62+
var builder = new StreamBuilder();
63+
64+
builder
65+
.Stream<string, string>("users")
66+
.GroupByKey()
67+
.WindowedBy(TumblingWindowOptions.Of(60000))
68+
.Aggregate(
69+
() => 0,
70+
(k, v, agg) => Math.Max(v.Length, agg),
71+
InMemoryWindows.As<string, int>("store").WithValueSerdes<Int32SerDes>());
72+
73+
return builder;
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using NUnit.Framework;
5+
using Streamiz.Kafka.Net.State.InMemory.Internal;
6+
7+
namespace Streamiz.Kafka.Net.Tests.Private;
8+
9+
public class ConcurrentSetTests
10+
{
11+
private ConcurrentSet<string> concurrentSet;
12+
13+
[SetUp]
14+
public void Init()
15+
{
16+
concurrentSet = new();
17+
}
18+
19+
[TearDown]
20+
public void Dispose()
21+
{
22+
concurrentSet.Clear();
23+
}
24+
25+
[TestCase(1000)]
26+
public void ConcurrencyAdded(int numberTasks)
27+
{
28+
var taskList = new List<Task>();
29+
for (int i = 0; i < numberTasks; i++)
30+
{
31+
taskList.Add(Task.Factory.StartNew((Object obj) =>
32+
{
33+
concurrentSet.Add(Guid.NewGuid().ToString());
34+
}, null));
35+
}
36+
Task.WaitAll(taskList.ToArray());
37+
Assert.AreEqual(numberTasks, concurrentSet.Count);
38+
}
39+
40+
[TestCase(1000)]
41+
public void ConcurrencyRemoved(int numberTasks)
42+
{
43+
for (int i = 0; i < numberTasks; i++)
44+
concurrentSet.Add(i.ToString());
45+
46+
var taskList = new List<Task>();
47+
for (int i = 0; i < numberTasks; i++)
48+
{
49+
taskList.Add(Task.Factory.StartNew((Object obj) =>
50+
{
51+
concurrentSet.Remove(obj.ToString());
52+
}, i));
53+
}
54+
55+
Task.WaitAll(taskList.ToArray());
56+
Assert.AreEqual(0, concurrentSet.Count);
57+
}
58+
59+
[TestCase(10000)]
60+
public void ConcurrencyAddedAndForeach(int numberTasks)
61+
{
62+
var taskList = new List<Task>();
63+
for (int i = 0; i < numberTasks; i++)
64+
{
65+
taskList.Add(Task.Factory.StartNew((Object obj) =>
66+
{
67+
concurrentSet.Add(Guid.NewGuid().ToString());
68+
foreach (var c in concurrentSet)
69+
;
70+
}, null));
71+
}
72+
Task.WaitAll(taskList.ToArray());
73+
Assert.AreEqual(numberTasks, concurrentSet.Count);
74+
}
75+
76+
[TestCase(10000)]
77+
public void ConcurrencyAddedAndContains(int numberTasks)
78+
{
79+
var taskList = new List<Task>();
80+
for (int i = 0; i < numberTasks; i++)
81+
{
82+
taskList.Add(Task.Factory.StartNew((Object obj) =>
83+
{
84+
var guid = Guid.NewGuid().ToString();
85+
concurrentSet.Add(guid);
86+
Assert.IsTrue(concurrentSet.Contains(guid));
87+
}, null));
88+
}
89+
Task.WaitAll(taskList.ToArray());
90+
Assert.AreEqual(numberTasks, concurrentSet.Count);
91+
}
92+
93+
}

0 commit comments

Comments
 (0)