aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/Linden
diff options
context:
space:
mode:
authorDiva Canto2014-12-30 20:05:33 -0800
committerDiva Canto2014-12-30 20:05:33 -0800
commit0af02efaed2e42f128f585b960550b0b8896c54f (patch)
tree886803e85dd278a62f9e42f3eca92d49bf785028 /OpenSim/Region/ClientStack/Linden
parentAdd support for expansion of key values in nini config files. (diff)
parentMerge branch 'mb-throttle-test' (diff)
downloadopensim-SC-0af02efaed2e42f128f585b960550b0b8896c54f.zip
opensim-SC-0af02efaed2e42f128f585b960550b0b8896c54f.tar.gz
opensim-SC-0af02efaed2e42f128f585b960550b0b8896c54f.tar.bz2
opensim-SC-0af02efaed2e42f128f585b960550b0b8896c54f.tar.xz
Merge branch 'master' of ssh://opensimulator.org/var/git/opensim
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden')
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs2
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs66
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs28
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs12
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs59
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs19
6 files changed, 149 insertions, 37 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs
index 8f14806..de91856 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs
@@ -229,7 +229,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
229 m_throttleClient 229 m_throttleClient
230 = new AdaptiveTokenBucket( 230 = new AdaptiveTokenBucket(
231 string.Format("adaptive throttle for {0} in {1}", AgentID, server.Scene.Name), 231 string.Format("adaptive throttle for {0} in {1}", AgentID, server.Scene.Name),
232 parentThrottle, 0, rates.Total, rates.AdaptiveThrottlesEnabled); 232 parentThrottle, 0, rates.Total, rates.MinimumAdaptiveThrottleRate, rates.AdaptiveThrottlesEnabled);
233 233
234 // Create an array of token buckets for this clients different throttle categories 234 // Create an array of token buckets for this clients different throttle categories
235 m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; 235 m_throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT];
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs b/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs
index 94300f8..9bee3ad 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs
@@ -107,6 +107,62 @@ namespace OpenMetaverse
107 /// </summary> 107 /// </summary>
108 public float AverageReceiveTicksForLastSamplePeriod { get; private set; } 108 public float AverageReceiveTicksForLastSamplePeriod { get; private set; }
109 109
110 #region PacketDropDebugging
111 /// <summary>
112 /// For debugging purposes only... random number generator for dropping
113 /// outbound packets.
114 /// </summary>
115 private Random m_dropRandomGenerator;
116
117 /// <summary>
118 /// For debugging purposes only... parameters for a simplified
119 /// model of packet loss with bursts, overall drop rate should
120 /// be roughly 1 - m_dropLengthProbability / (m_dropProbabiliy + m_dropLengthProbability)
121 /// which is about 1% for parameters 0.0015 and 0.15
122 /// </summary>
123 private double m_dropProbability = 0.0030;
124 private double m_dropLengthProbability = 0.15;
125 private bool m_dropState = false;
126
127 /// <summary>
128 /// For debugging purposes only... parameters to control the time
129 /// duration over which packet loss bursts can occur, if no packets
130 /// have been sent for m_dropResetTicks milliseconds, then reset the
131 /// state of the packet dropper to its default.
132 /// </summary>
133 private int m_dropLastTick = 0;
134 private int m_dropResetTicks = 500;
135
136 /// <summary>
137 /// Debugging code used to simulate dropped packets with bursts
138 /// </summary>
139 private bool DropOutgoingPacket()
140 {
141 double rnum = m_dropRandomGenerator.NextDouble();
142
143 // if the connection has been idle for awhile (more than m_dropResetTicks) then
144 // reset the state to the default state, don't continue a burst
145 int curtick = Util.EnvironmentTickCount();
146 if (Util.EnvironmentTickCountSubtract(curtick, m_dropLastTick) > m_dropResetTicks)
147 m_dropState = false;
148
149 m_dropLastTick = curtick;
150
151 // if we are dropping packets, then the probability of dropping
152 // this packet is the probability that we stay in the burst
153 if (m_dropState)
154 {
155 m_dropState = (rnum < (1.0 - m_dropLengthProbability)) ? true : false;
156 }
157 else
158 {
159 m_dropState = (rnum < m_dropProbability) ? true : false;
160 }
161
162 return m_dropState;
163 }
164 #endregion PacketDropDebugging
165
110 /// <summary> 166 /// <summary>
111 /// Default constructor 167 /// Default constructor
112 /// </summary> 168 /// </summary>
@@ -117,6 +173,10 @@ namespace OpenMetaverse
117 { 173 {
118 m_localBindAddress = bindAddress; 174 m_localBindAddress = bindAddress;
119 m_udpPort = port; 175 m_udpPort = port;
176
177 // for debugging purposes only, initializes the random number generator
178 // used for simulating packet loss
179 // m_dropRandomGenerator = new Random();
120 } 180 }
121 181
122 /// <summary> 182 /// <summary>
@@ -395,6 +455,12 @@ namespace OpenMetaverse
395 { 455 {
396// if (IsRunningOutbound) 456// if (IsRunningOutbound)
397// { 457// {
458
459 // This is strictly for debugging purposes to simulate dropped
460 // packets when testing throttles & retransmission code
461 // if (DropOutgoingPacket())
462 // return;
463
398 try 464 try
399 { 465 {
400 m_udpSocket.BeginSendTo( 466 m_udpSocket.BeginSendTo(
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs
index 0560b9b..3c82a78 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/Tests/ThrottleTests.cs
@@ -141,7 +141,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests
141 141
142 udpServer.Throttle.DebugLevel = 1; 142 udpServer.Throttle.DebugLevel = 1;
143 udpClient.ThrottleDebugLevel = 1; 143 udpClient.ThrottleDebugLevel = 1;
144 144
145 int resendBytes = 1000; 145 int resendBytes = 1000;
146 int landBytes = 2000; 146 int landBytes = 2000;
147 int windBytes = 3000; 147 int windBytes = 3000;
@@ -173,6 +173,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests
173 IniConfigSource ics = new IniConfigSource(); 173 IniConfigSource ics = new IniConfigSource();
174 IConfig config = ics.AddConfig("ClientStack.LindenUDP"); 174 IConfig config = ics.AddConfig("ClientStack.LindenUDP");
175 config.Set("enable_adaptive_throttles", true); 175 config.Set("enable_adaptive_throttles", true);
176 config.Set("adaptive_throttle_min_bps", 32000);
177
176 TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene, ics); 178 TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene, ics);
177 179
178 ScenePresence sp 180 ScenePresence sp
@@ -184,8 +186,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests
184 udpServer.Throttle.DebugLevel = 1; 186 udpServer.Throttle.DebugLevel = 1;
185 udpClient.ThrottleDebugLevel = 1; 187 udpClient.ThrottleDebugLevel = 1;
186 188
187 // Total is 280000 189 // Total is 275000
188 int resendBytes = 10000; 190 int resendBytes = 5000; // this is set low to test the minimum throttle override
189 int landBytes = 20000; 191 int landBytes = 20000;
190 int windBytes = 30000; 192 int windBytes = 30000;
191 int cloudBytes = 40000; 193 int cloudBytes = 40000;
@@ -197,28 +199,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests
197 SetThrottles( 199 SetThrottles(
198 udpClient, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); 200 udpClient, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes);
199 201
200 // Ratio of current adaptive drip rate to requested bytes 202 // Ratio of current adaptive drip rate to requested bytes, minimum rate is 32000
201 // XXX: Should hard code this as below so we don't rely on values given by tested code to construct 203 double commitRatio = 32000.0 / totalBytes;
202 // expected values.
203 double commitRatio = (double)udpClient.FlowThrottle.AdjustedDripRate / udpClient.FlowThrottle.TargetDripRate;
204 204
205 AssertThrottles( 205 AssertThrottles(
206 udpClient, 206 udpClient,
207 LLUDPServer.MTU, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio, 207 LLUDPServer.MTU, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio,
208 textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0); 208 textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0);
209 209
210 // Test an increase in target throttle 210 // Test an increase in target throttle, ack of 20 packets adds 20 * LLUDPServer.MTU bytes
211 udpClient.FlowThrottle.AcknowledgePackets(35000); 211 // to the throttle, recompute commitratio from those numbers
212 commitRatio = 0.2; 212 udpClient.FlowThrottle.AcknowledgePackets(20);
213 commitRatio = (32000.0 + 20.0 * LLUDPServer.MTU) / totalBytes;
213 214
214 AssertThrottles( 215 AssertThrottles(
215 udpClient, 216 udpClient,
216 resendBytes * commitRatio, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio, 217 LLUDPServer.MTU, landBytes * commitRatio, windBytes * commitRatio, cloudBytes * commitRatio, taskBytes * commitRatio,
217 textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0); 218 textureBytes * commitRatio, assetBytes * commitRatio, udpClient.FlowThrottle.AdjustedDripRate, totalBytes, 0);
218 219
219 // Test a decrease in target throttle 220 // Test a decrease in target throttle, adaptive throttle should cut the rate by 50% with a floor
221 // set by the minimum adaptive rate
220 udpClient.FlowThrottle.ExpirePackets(1); 222 udpClient.FlowThrottle.ExpirePackets(1);
221 commitRatio = 0.1; 223 commitRatio = (32000.0 + (20.0 * LLUDPServer.MTU)/Math.Pow(2,1)) / totalBytes;
222 224
223 AssertThrottles( 225 AssertThrottles(
224 udpClient, 226 udpClient,
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs b/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs
index dd15cc7..7a2756b 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/ThrottleRates.cs
@@ -58,7 +58,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
58 58
59 /// <summary>Flag used to enable adaptive throttles</summary> 59 /// <summary>Flag used to enable adaptive throttles</summary>
60 public bool AdaptiveThrottlesEnabled; 60 public bool AdaptiveThrottlesEnabled;
61 61
62 /// <summary>
63 /// Set the minimum rate that the adaptive throttles can set. The viewer
64 /// can still throttle lower than this, but the adaptive throttles will
65 /// never decrease rates below this no matter how many packets are dropped
66 /// </summary>
67 public Int64 MinimumAdaptiveThrottleRate;
68
62 /// <summary>Amount of the texture throttle to steal for the task throttle</summary> 69 /// <summary>Amount of the texture throttle to steal for the task throttle</summary>
63 public double CannibalizeTextureRate; 70 public double CannibalizeTextureRate;
64 71
@@ -84,7 +91,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
84 Total = throttleConfig.GetInt("client_throttle_max_bps", 0); 91 Total = throttleConfig.GetInt("client_throttle_max_bps", 0);
85 92
86 AdaptiveThrottlesEnabled = throttleConfig.GetBoolean("enable_adaptive_throttles", false); 93 AdaptiveThrottlesEnabled = throttleConfig.GetBoolean("enable_adaptive_throttles", false);
87 94 MinimumAdaptiveThrottleRate = throttleConfig.GetInt("adaptive_throttle_min_bps", 32000);
95
88 CannibalizeTextureRate = (double)throttleConfig.GetFloat("CannibalizeTextureRate", 0.0f); 96 CannibalizeTextureRate = (double)throttleConfig.GetFloat("CannibalizeTextureRate", 0.0f);
89 CannibalizeTextureRate = Util.Clamp<double>(CannibalizeTextureRate,0.0, 0.9); 97 CannibalizeTextureRate = Util.Clamp<double>(CannibalizeTextureRate,0.0, 0.9);
90 } 98 }
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs b/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
index 4ca83ff..4616203 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/TokenBucket.cs
@@ -61,7 +61,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
61 61
62 /// <summary> 62 /// <summary>
63 /// </summary> 63 /// </summary>
64 protected const Int32 m_minimumDripRate = 1400; 64 protected const Int32 m_minimumDripRate = LLUDPServer.MTU;
65 65
66 /// <summary>Time of the last drip, in system ticks</summary> 66 /// <summary>Time of the last drip, in system ticks</summary>
67 protected Int32 m_lastDrip; 67 protected Int32 m_lastDrip;
@@ -392,13 +392,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP
392 } 392 }
393 393
394 /// <summary> 394 /// <summary>
395 /// The minimum rate for flow control. Minimum drip rate is one 395 /// The minimum rate for adaptive flow control.
396 /// packet per second. Open the throttle to 15 packets per second
397 /// or about 160kbps.
398 /// </summary> 396 /// </summary>
399 protected const Int64 m_minimumFlow = m_minimumDripRate * 15; 397 protected Int64 m_minimumFlow = 32000;
400 398
401 public AdaptiveTokenBucket(string identifier, TokenBucket parent, Int64 requestedDripRate, Int64 maxDripRate, bool enabled) 399 /// <summary>
400 /// Constructor for the AdaptiveTokenBucket class
401 /// <param name="identifier">Unique identifier for the client</param>
402 /// <param name="parent">Parent bucket in the hierarchy</param>
403 /// <param name="requestedDripRate"></param>
404 /// <param name="maxDripRate">The ceiling rate for adaptation</param>
405 /// <param name="minDripRate">The floor rate for adaptation</param>
406 /// </summary>
407 public AdaptiveTokenBucket(string identifier, TokenBucket parent, Int64 requestedDripRate, Int64 maxDripRate, Int64 minDripRate, bool enabled)
402 : base(identifier, parent, requestedDripRate, maxDripRate) 408 : base(identifier, parent, requestedDripRate, maxDripRate)
403 { 409 {
404 AdaptiveEnabled = enabled; 410 AdaptiveEnabled = enabled;
@@ -406,34 +412,53 @@ namespace OpenSim.Region.ClientStack.LindenUDP
406 if (AdaptiveEnabled) 412 if (AdaptiveEnabled)
407 { 413 {
408// m_log.DebugFormat("[TOKENBUCKET]: Adaptive throttle enabled"); 414// m_log.DebugFormat("[TOKENBUCKET]: Adaptive throttle enabled");
415 m_minimumFlow = minDripRate;
409 TargetDripRate = m_minimumFlow; 416 TargetDripRate = m_minimumFlow;
410 AdjustedDripRate = m_minimumFlow; 417 AdjustedDripRate = m_minimumFlow;
411 } 418 }
412 } 419 }
413 420
414 // <summary> 421 /// <summary>
415 // Reliable packets sent to the client for which we never received an ack adjust the drip rate down. 422 /// Reliable packets sent to the client for which we never received an ack adjust the drip rate down.
416 // </summary> 423 /// <param name="packets">Number of packets that expired without successful delivery</param>
417 public void ExpirePackets(Int32 count) 424 /// </summary>
425 public void ExpirePackets(Int32 packets)
418 { 426 {
419 if (AdaptiveEnabled) 427 if (AdaptiveEnabled)
420 { 428 {
421 if (DebugLevel > 0) 429 if (DebugLevel > 0)
422 m_log.WarnFormat( 430 m_log.WarnFormat(
423 "[ADAPTIVEBUCKET] drop {0} by {1} expired packets for {2}", 431 "[ADAPTIVEBUCKET] drop {0} by {1} expired packets for {2}",
424 AdjustedDripRate, count, Identifier); 432 AdjustedDripRate, packets, Identifier);
425 433
426 AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,count)); 434 // AdjustedDripRate = (Int64) (AdjustedDripRate / Math.Pow(2,packets));
435
436 // Compute the fallback solely on the rate allocated beyond the minimum, this
437 // should smooth out the fallback to the minimum rate
438 AdjustedDripRate = m_minimumFlow + (Int64) ((AdjustedDripRate - m_minimumFlow) / Math.Pow(2, packets));
427 } 439 }
428 } 440 }
429 441
430 // <summary> 442 /// <summary>
431 // Reliable packets acked by the client adjust the drip rate up. 443 /// Reliable packets acked by the client adjust the drip rate up.
432 // </summary> 444 /// <param name="packets">Number of packets successfully acknowledged</param>
433 public void AcknowledgePackets(Int32 count) 445 /// </summary>
446 public void AcknowledgePackets(Int32 packets)
434 { 447 {
435 if (AdaptiveEnabled) 448 if (AdaptiveEnabled)
436 AdjustedDripRate = AdjustedDripRate + count; 449 AdjustedDripRate = AdjustedDripRate + packets * LLUDPServer.MTU;
450 }
451
452 /// <summary>
453 /// Adjust the minimum flow level for the adaptive throttle, this will drop adjusted
454 /// throttles back to the minimum levels
455 /// <param>minDripRate--the new minimum flow</param>
456 /// </summary>
457 public void ResetMinimumAdaptiveFlow(Int64 minDripRate)
458 {
459 m_minimumFlow = minDripRate;
460 TargetDripRate = m_minimumFlow;
461 AdjustedDripRate = m_minimumFlow;
437 } 462 }
438 } 463 }
439} \ No newline at end of file 464} \ No newline at end of file
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs b/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs
index 9d6c09e..b546a99 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/UnackedPacketCollection.cs
@@ -31,6 +31,9 @@ using System.Net;
31using System.Threading; 31using System.Threading;
32using OpenMetaverse; 32using OpenMetaverse;
33 33
34//using System.Reflection;
35//using log4net;
36
34namespace OpenSim.Region.ClientStack.LindenUDP 37namespace OpenSim.Region.ClientStack.LindenUDP
35{ 38{
36 /// <summary> 39 /// <summary>
@@ -60,6 +63,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
60 } 63 }
61 } 64 }
62 65
66 //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
67
63 /// <summary>Holds the actual unacked packet data, sorted by sequence number</summary> 68 /// <summary>Holds the actual unacked packet data, sorted by sequence number</summary>
64 private Dictionary<uint, OutgoingPacket> m_packets = new Dictionary<uint, OutgoingPacket>(); 69 private Dictionary<uint, OutgoingPacket> m_packets = new Dictionary<uint, OutgoingPacket>();
65 /// <summary>Holds packets that need to be added to the unacknowledged list</summary> 70 /// <summary>Holds packets that need to be added to the unacknowledged list</summary>
@@ -164,8 +169,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
164 } 169 }
165 } 170 }
166 171
167 //if (expiredPackets != null) 172 // if (expiredPackets != null)
168 // m_log.DebugFormat("[UNACKED PACKET COLLECTION]: Found {0} expired packets on timeout of {1}", expiredPackets.Count, timeoutMS); 173 // m_log.DebugFormat("[UNACKED PACKET COLLECTION]: Found {0} expired packets on timeout of {1}", expiredPackets.Count, timeoutMS);
169 174
170 return expiredPackets; 175 return expiredPackets;
171 } 176 }
@@ -192,7 +197,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
192 197
193 // As with other network applications, assume that an acknowledged packet is an 198 // As with other network applications, assume that an acknowledged packet is an
194 // indication that the network can handle a little more load, speed up the transmission 199 // indication that the network can handle a little more load, speed up the transmission
195 ackedPacket.Client.FlowThrottle.AcknowledgePackets(ackedPacket.Buffer.DataLength); 200 ackedPacket.Client.FlowThrottle.AcknowledgePackets(1);
196 201
197 // Update stats 202 // Update stats
198 Interlocked.Add(ref ackedPacket.Client.UnackedBytes, -ackedPacket.Buffer.DataLength); 203 Interlocked.Add(ref ackedPacket.Client.UnackedBytes, -ackedPacket.Buffer.DataLength);
@@ -207,9 +212,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP
207 } 212 }
208 else 213 else
209 { 214 {
210 //m_log.WarnFormat("[UNACKED PACKET COLLECTION]: Could not find packet with sequence number {0} to ack", pendingAcknowledgement.SequenceNumber); 215 // m_log.WarnFormat("[UNACKED PACKET COLLECTION]: found null packet for sequence number {0} to ack",
216 // pendingAcknowledgement.SequenceNumber);
211 } 217 }
212 } 218 }
219 else
220 {
221 // m_log.WarnFormat("[UNACKED PACKET COLLECTION]: Could not find packet with sequence number {0} to ack",
222 // pendingAcknowledgement.SequenceNumber);
223 }
213 } 224 }
214 225
215 uint pendingRemove; 226 uint pendingRemove;