Skip to content

Commit a50f9a2

Browse files
committed
Modified the ImapToken caching logic to include qstring tokens
Also reduced memory allocations in ImapTokenCache.AddOrGet()
1 parent ee1b988 commit a50f9a2

File tree

3 files changed

+89
-48
lines changed

3 files changed

+89
-48
lines changed

MailKit/Net/Imap/ImapStream.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -587,9 +587,7 @@ ImapToken ReadQuotedStringToken (CancellationToken cancellationToken)
587587
while (!TryReadQuotedString (builder, ref escaped))
588588
ReadAhead (2, cancellationToken);
589589

590-
var qstring = builder.ToString ();
591-
592-
return ImapToken.Create (ImapTokenType.QString, qstring);
590+
return ImapToken.Create (ImapTokenType.QString, builder);
593591
}
594592
}
595593

@@ -604,9 +602,7 @@ async ValueTask<ImapToken> ReadQuotedStringTokenAsync (CancellationToken cancell
604602
while (!TryReadQuotedString (builder, ref escaped))
605603
await ReadAheadAsync (2, cancellationToken).ConfigureAwait (false);
606604

607-
var qstring = builder.ToString ();
608-
609-
return ImapToken.Create (ImapTokenType.QString, qstring);
605+
return ImapToken.Create (ImapTokenType.QString, builder);
610606
}
611607
}
612608

@@ -739,7 +735,7 @@ ImapToken ReadLiteralToken (CancellationToken cancellationToken)
739735
inputIndex++;
740736

741737
if (!builder.TryParse (1, endIndex, out literalDataLeft))
742-
return ImapToken.Create (ImapTokenType.Error, builder.ToString ());
738+
return ImapToken.CreateError (builder);
743739

744740
Mode = ImapStreamMode.Literal;
745741

@@ -781,7 +777,7 @@ async ValueTask<ImapToken> ReadLiteralTokenAsync (CancellationToken cancellation
781777
inputIndex++;
782778

783779
if (!builder.TryParse (1, endIndex, out literalDataLeft) || literalDataLeft < 0)
784-
return ImapToken.Create (ImapTokenType.Error, builder.ToString ());
780+
return ImapToken.CreateError (builder);
785781

786782
Mode = ImapStreamMode.Literal;
787783

MailKit/Net/Imap/ImapToken.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,19 @@ public static ImapToken Create (ImapTokenType type, int literalLength)
127127
return new ImapToken (type, literalLength);
128128
}
129129

130+
static bool IsAscii (ByteArrayBuilder builder)
131+
{
132+
for (int i = 0; i < builder.Length; i++) {
133+
byte c = builder[i];
134+
135+
// Disregard any non-ASCII tokens.
136+
if (c < 32 || c >= 127)
137+
return false;
138+
}
139+
140+
return true;
141+
}
142+
130143
static bool IsCacheable (ByteArrayBuilder builder)
131144
{
132145
if (builder.Length < 2 || builder.Length > 32)
@@ -140,19 +153,12 @@ static bool IsCacheable (ByteArrayBuilder builder)
140153
if (builder[0] >= (byte) 'A' && builder[0] <= (byte) 'Z' && builder[1] >= (byte) '0' && builder[1] <= (byte) '9')
141154
return false;
142155

143-
for (int i = 0; i < builder.Length; i++) {
144-
byte c = (byte) builder[i];
145-
146-
// Disregard any non-ASCII "atoms".
147-
if (c <= 32 || c >= 127)
148-
return false;
149-
}
150-
151-
return true;
156+
return IsAscii (builder);
152157
}
153158

154159
public static ImapToken Create (ImapTokenType type, ByteArrayBuilder builder)
155160
{
161+
bool cachable = false;
156162
string value;
157163

158164
if (type == ImapTokenType.Flag) {
@@ -162,6 +168,8 @@ public static ImapToken Create (ImapTokenType type, ByteArrayBuilder builder)
162168
if (builder.Equals (value, true))
163169
return token;
164170
}
171+
172+
cachable = IsAscii (builder);
165173
} else if (type == ImapTokenType.Atom) {
166174
if (builder.Equals ("NIL", true)) {
167175
// Look for the cached NIL token that matches this capitalization.
@@ -207,19 +215,23 @@ public static ImapToken Create (ImapTokenType type, ByteArrayBuilder builder)
207215
return XGMMsgId;
208216
if (builder.Equals ("X-GM-THRID", false))
209217
return XGMThrId;
218+
219+
cachable = IsCacheable (builder);
220+
} else if (type == ImapTokenType.QString) {
221+
cachable = IsAscii (builder);
210222
}
211223

212-
if (IsCacheable (builder))
224+
if (cachable)
213225
return Cache.AddOrGet (type, builder);
214226

215227
value = builder.ToString ();
216228

217229
return new ImapToken (type, value);
218230
}
219231

220-
public static ImapToken Create (ImapTokenType type, string value)
232+
public static ImapToken CreateError (ByteArrayBuilder builder)
221233
{
222-
return new ImapToken (type, value);
234+
return new ImapToken (ImapTokenType.Error, builder.ToString ());
223235
}
224236

225237
public override string ToString ()

MailKit/Net/Imap/ImapTokenCache.cs

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,44 +36,49 @@ class ImapTokenCache
3636

3737
readonly Dictionary<ImapTokenKey, LinkedListNode<ImapTokenItem>> cache;
3838
readonly LinkedList<ImapTokenItem> list;
39+
readonly ImapTokenKey lookupKey;
3940

4041
public ImapTokenCache ()
4142
{
4243
cache = new Dictionary<ImapTokenKey, LinkedListNode<ImapTokenItem>> ();
4344
list = new LinkedList<ImapTokenItem> ();
45+
lookupKey = new ImapTokenKey ();
4446
}
4547

4648
public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
4749
{
48-
// Note: This ImapTokenKey .ctor does not duplicate the buffer and is meant as a temporary key
49-
// in order to avoid memory allocations for lookup purposes.
50-
var key = new ImapTokenKey (builder.GetBuffer (), builder.Length);
51-
5250
lock (cache) {
53-
if (cache.TryGetValue (key, out var node)) {
51+
// lookupKey is a pre-allocated key used for lookups
52+
lookupKey.Init (type, builder.GetBuffer (), builder.Length);
53+
54+
if (cache.TryGetValue (lookupKey, out var node)) {
5455
// move the node to the head of the list
5556
list.Remove (node);
5657
list.AddFirst (node);
58+
node.Value.Count++;
5759

5860
return node.Value.Token;
5961
}
6062

63+
var token = new ImapToken (type, builder.ToString ());
64+
6165
if (cache.Count >= capacity) {
6266
// remove the least recently used token
6367
node = list.Last;
6468
list.RemoveLast ();
6569
cache.Remove (node.Value.Key);
66-
}
6770

68-
var token = new ImapToken (type, builder.ToString ());
69-
70-
// Note: We recreate the key here so we have a permanent key. Also this allows for reuse of the token's Value string.
71-
key = new ImapTokenKey ((string) token.Value);
71+
// re-use the node, item and key to avoid allocations
72+
node.Value.Key.Init (type, (string) token.Value);
73+
node.Value.Token = token;
74+
} else {
75+
var key = new ImapTokenKey (type, (string) token.Value);
76+
var item = new ImapTokenItem (key, token);
7277

73-
var item = new ImapTokenItem (key, token);
78+
node = new LinkedListNode<ImapTokenItem> (item);
79+
}
7480

75-
node = new LinkedListNode<ImapTokenItem> (item);
76-
cache.Add (key, node);
81+
cache.Add (node.Value.Key, node);
7782
list.AddFirst (node);
7883

7984
return token;
@@ -82,33 +87,49 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder)
8287

8388
class ImapTokenKey
8489
{
85-
readonly byte[] byteArrayKey;
86-
readonly string stringKey;
87-
readonly int length;
88-
readonly int hashCode;
90+
ImapTokenType type;
91+
byte[] byteArrayKey;
92+
string stringKey;
93+
int length;
94+
int hashCode;
95+
96+
public ImapTokenKey ()
97+
{
98+
}
8999

90-
public ImapTokenKey (byte[] key, int len)
100+
public ImapTokenKey (ImapTokenType type, string key)
91101
{
92-
byteArrayKey = key;
93-
length = len;
102+
Init (type, key);
103+
}
104+
105+
public void Init (ImapTokenType type, byte[] key, int length)
106+
{
107+
this.type = type;
108+
this.byteArrayKey = key;
109+
this.stringKey = null;
110+
this.length = length;
94111

95112
var hash = new HashCode ();
113+
hash.Add ((int) type);
96114
for (int i = 0; i < length; i++)
97115
hash.Add ((char) key[i]);
98116

99-
hashCode = hash.ToHashCode ();
117+
this.hashCode = hash.ToHashCode ();
100118
}
101119

102-
public ImapTokenKey (string key)
120+
public void Init (ImapTokenType type, string key)
103121
{
104-
stringKey = key;
105-
length = key.Length;
122+
this.type = type;
123+
this.byteArrayKey = null;
124+
this.stringKey = key;
125+
this.length = key.Length;
106126

107127
var hash = new HashCode ();
128+
hash.Add ((int) type);
108129
for (int i = 0; i < length; i++)
109130
hash.Add (key[i]);
110131

111-
hashCode = hash.ToHashCode ();
132+
this.hashCode = hash.ToHashCode ();
112133
}
113134

114135
static bool Equals (string str, byte[] bytes)
@@ -123,7 +144,7 @@ static bool Equals (string str, byte[] bytes)
123144

124145
static bool Equals (ImapTokenKey self, ImapTokenKey other)
125146
{
126-
if (self.length != other.length)
147+
if (self.type != other.type || self.length != other.length)
127148
return false;
128149

129150
if (self.stringKey != null) {
@@ -153,17 +174,29 @@ public override int GetHashCode ()
153174
{
154175
return hashCode;
155176
}
177+
178+
public override string ToString ()
179+
{
180+
return string.Format ("{0}: {1}", type, stringKey ?? Encoding.UTF8.GetString (byteArrayKey, 0, length));
181+
}
156182
}
157183

158184
class ImapTokenItem
159185
{
160-
public readonly ImapTokenKey Key;
161-
public readonly ImapToken Token;
186+
public ImapTokenKey Key;
187+
public ImapToken Token;
188+
public int Count;
162189

163190
public ImapTokenItem (ImapTokenKey key, ImapToken token)
164191
{
165192
Key = key;
166193
Token = token;
194+
Count = 1;
195+
}
196+
197+
public override string ToString ()
198+
{
199+
return $"{Count}";
167200
}
168201
}
169202
}

0 commit comments

Comments
 (0)