Initial commit
[mono.git] / mcs / class / referencesource / mscorlib / system / runtime / remoting / lease.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 //+----------------------------------------------------------------------------
7 //
8 // Microsoft Windows
9 // File:        Lease.cs
10 //
11 // Contents:    Lease class
12 //
13 // History:     1/5/00   <EMAIL>[....]</EMAIL>        Created
14 //
15 //+----------------------------------------------------------------------------
16
17 namespace System.Runtime.Remoting.Lifetime
18 {
19     using System;
20     using System.Security;
21     using System.Security.Permissions;
22     using System.Collections;
23     using System.Threading;
24     using System.Runtime.Remoting.Messaging;
25     using System.Runtime.Remoting.Proxies;
26     using System.Globalization;
27
28     internal class Lease : MarshalByRefObject, ILease
29     {
30         internal int id = 0;
31         
32         // Lease Time
33         internal DateTime leaseTime;
34         internal TimeSpan initialLeaseTime;
35         
36         // Renewal Policies
37         internal TimeSpan renewOnCallTime;
38         internal TimeSpan sponsorshipTimeout;
39
40         // Sponsors
41         internal Hashtable sponsorTable;
42         internal int sponsorCallThread;
43
44         // Links to leasemanager and managed object
45         internal LeaseManager leaseManager;
46         internal MarshalByRefObject managedObject;
47
48         // State
49         internal LeaseState state;
50
51         internal static volatile int nextId = 0;        
52
53
54         internal Lease(TimeSpan initialLeaseTime,
55                        TimeSpan renewOnCallTime,                       
56                        TimeSpan sponsorshipTimeout,
57                        MarshalByRefObject managedObject
58                       )
59         {
60             id = nextId++;
61             BCLDebug.Trace("REMOTE", "Lease Constructor ",managedObject," initialLeaseTime "+initialLeaseTime+" renewOnCall "+renewOnCallTime+" sponsorshipTimeout ",sponsorshipTimeout);
62
63             // Set Policy            
64             this.renewOnCallTime = renewOnCallTime;
65             this.sponsorshipTimeout = sponsorshipTimeout;
66             this.initialLeaseTime = initialLeaseTime;
67             this.managedObject = managedObject;
68
69             //Add lease to leaseManager
70             leaseManager = LeaseManager.GetLeaseManager();
71
72             // Initialize tables
73             sponsorTable = new Hashtable(10);
74             state = LeaseState.Initial;
75         }
76
77         internal void ActivateLease()
78         {
79             // Set leaseTime
80             leaseTime = DateTime.UtcNow.Add(initialLeaseTime);
81             state = LeaseState.Active;
82             leaseManager.ActivateLease(this);
83         }
84
85         // Override MarshalByRefObject InitializeLifetimeService
86         // Don't want a lease on a lease therefore returns null
87         [System.Security.SecurityCritical]  // auto-generated
88         public override Object InitializeLifetimeService()
89         {
90             BCLDebug.Trace("REMOTE", "Lease ",id," InitializeLifetimeService, lease Marshalled");
91             return null;
92         }
93
94         // ILease Property and Methods
95
96         public TimeSpan RenewOnCallTime
97         {
98             [System.Security.SecurityCritical]  // auto-generated
99             get { return renewOnCallTime; }
100             [System.Security.SecurityCritical]  // auto-generated
101             [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
102             set
103             {
104                 if (state == LeaseState.Initial)
105                 {
106                     renewOnCallTime = value;
107                     BCLDebug.Trace("REMOTE", "Lease Set RenewOnCallProperty ",managedObject," "+renewOnCallTime);
108                 }
109                 else
110                     throw new RemotingException(Environment.GetResourceString("Remoting_Lifetime_InitialStateRenewOnCall", ((Enum)state).ToString()));                    
111             }
112         }
113
114         public TimeSpan SponsorshipTimeout
115         {
116             [System.Security.SecurityCritical]  // auto-generated
117             get { return sponsorshipTimeout; }
118             [System.Security.SecurityCritical]  // auto-generated
119             [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
120             set
121             {
122                 if (state == LeaseState.Initial)
123                 {
124                     sponsorshipTimeout = value;
125                     BCLDebug.Trace("REMOTE", "Lease Set SponsorshipTimeout Property ",managedObject," "+sponsorshipTimeout);                    
126                 }
127                 else
128                     throw new RemotingException(Environment.GetResourceString("Remoting_Lifetime_InitialStateSponsorshipTimeout", ((Enum)state).ToString()));                                        
129             }
130         }
131
132         public TimeSpan InitialLeaseTime
133         {
134             [System.Security.SecurityCritical]  // auto-generated
135             get { return initialLeaseTime; }
136
137             [System.Security.SecurityCritical]  // auto-generated
138             [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
139             set
140             {
141                 if (state == LeaseState.Initial)
142                 {
143                     initialLeaseTime = value;
144                     if (TimeSpan.Zero.CompareTo(value) >= 0)
145                         state = LeaseState.Null;
146                     BCLDebug.Trace("REMOTE", "Lease Set InitialLeaseTime Property ",managedObject,"  "+InitialLeaseTime+", current state "+((Enum)state).ToString());                                                            
147                 }
148                 else
149                     throw new RemotingException(Environment.GetResourceString("Remoting_Lifetime_InitialStateInitialLeaseTime", ((Enum)state).ToString()));                                                            
150             }
151         }
152
153         public TimeSpan CurrentLeaseTime
154         {
155             [System.Security.SecurityCritical]  // auto-generated
156             get { return leaseTime.Subtract(DateTime.UtcNow); }
157         }
158
159         public LeaseState CurrentState
160         {
161             [System.Security.SecurityCritical]  // auto-generated
162             get { return state;}
163         }        
164
165
166         [System.Security.SecurityCritical]  // auto-generated
167         [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
168         public void Register(ISponsor obj)
169         {
170             Register(obj, TimeSpan.Zero);
171         }
172         
173         [System.Security.SecurityCritical]  // auto-generated
174         [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
175         public void Register(ISponsor obj, TimeSpan renewalTime)
176         {
177             lock(this)
178             {
179                 BCLDebug.Trace("REMOTE", "Lease "+id+" Register Sponsor  renewalTime ",renewalTime," state ",((Enum)state).ToString());
180                 if (state == LeaseState.Expired || sponsorshipTimeout == TimeSpan.Zero)
181                     return;
182
183                 Object sponsorId = GetSponsorId(obj);
184                 lock(sponsorTable)
185                 {
186                     if (renewalTime > TimeSpan.Zero)
187                         AddTime(renewalTime);
188                     if (!sponsorTable.ContainsKey(sponsorId))
189                     {
190                         // Place in tables
191                         sponsorTable[sponsorId] = new SponsorStateInfo(renewalTime, SponsorState.Initial);
192                     }
193                 }
194             }
195         }
196
197         [System.Security.SecurityCritical]  // auto-generated
198         [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
199         public void Unregister(ISponsor sponsor)
200         {
201             lock(this)
202             {
203                 BCLDebug.Trace("REMOTE", "Lease",id," Unregister  state ",((Enum)state).ToString());
204                 if (state == LeaseState.Expired)
205                     return;
206
207                 Object sponsorId = GetSponsorId(sponsor);
208                 lock(sponsorTable)
209                 {
210                     if (sponsorId != null)
211                     {
212                         leaseManager.DeleteSponsor(sponsorId);                
213                         SponsorStateInfo sponsorStateInfo = (SponsorStateInfo)sponsorTable[sponsorId];
214                         sponsorTable.Remove(sponsorId);
215                     }
216                 }
217             }
218         }
219
220         // Get the local representative of the sponsor to prevent a remote access when placing
221         // in a hash table.
222         [System.Security.SecurityCritical]  // auto-generated
223         private Object GetSponsorId(ISponsor obj)
224         {
225             Object sponsorId = null;
226             if (obj != null)
227             {
228                 if (RemotingServices.IsTransparentProxy(obj))
229                     sponsorId = RemotingServices.GetRealProxy(obj);
230                 else
231                     sponsorId = obj;
232             }
233             return sponsorId;
234         }
235
236         // Convert from the local representative of the sponsor to either the MarshalByRefObject or local object
237         [System.Security.SecurityCritical]  // auto-generated
238         private ISponsor GetSponsorFromId(Object sponsorId)
239         {
240             Object sponsor = null;
241             RealProxy rp = sponsorId as RealProxy;
242             if (null != rp)
243                 sponsor = rp.GetTransparentProxy();
244             else
245                 sponsor = sponsorId;
246             return (ISponsor)sponsor;
247         }
248         
249         [System.Security.SecurityCritical]  // auto-generated
250         [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration)]
251         public TimeSpan Renew(TimeSpan renewalTime)
252         {
253             return RenewInternal(renewalTime);
254         }
255
256         // We will call this internally within the server domain
257         internal TimeSpan RenewInternal(TimeSpan renewalTime)
258         {
259             lock(this)
260             {
261                 BCLDebug.Trace("REMOTE","Lease ",id," Renew ",renewalTime," state ",((Enum)state).ToString());
262                 if (state == LeaseState.Expired)
263                     return TimeSpan.Zero;
264                 AddTime(renewalTime);
265                 return leaseTime.Subtract(DateTime.UtcNow);
266             }
267         }
268
269         // Used for a lease which has been created, but will not be used
270         internal void Remove()
271         {
272             BCLDebug.Trace("REMOTE","Lease ",id," Remove state ",((Enum)state).ToString());
273             if (state == LeaseState.Expired)
274                 return;
275             state = LeaseState.Expired;            
276             leaseManager.DeleteLease(this);
277         }
278
279         [System.Security.SecurityCritical]  // auto-generated
280         internal void Cancel()
281         {
282             lock(this)
283             {                        
284                 BCLDebug.Trace("REMOTE","Lease ",id," Cancel Managed Object ",managedObject," state ",((Enum)state).ToString());
285
286                 if (state == LeaseState.Expired)
287                     return;
288
289                 Remove();
290                 // Disconnect the object ... 
291                 // We use the internal version of Disconnect passing "false"
292                 // for the bResetURI flag. This allows the object to keep its 
293                 // old URI in case its lease gets reactivated later.
294                 RemotingServices.Disconnect(managedObject, false);
295
296                 // Disconnect the lease for the object.
297                 RemotingServices.Disconnect(this);
298             }
299         }
300
301
302
303 #if _DEBUG
304         ~Lease()
305         {
306             BCLDebug.Trace("REMOTE","Lease ",id," Finalize");
307         }
308 #endif
309
310         internal void RenewOnCall()
311         {
312             lock(this)
313             {
314                 //BCLDebug.Trace("REMOTE","Lease ",id," RenewOnCall state ",((Enum)state).ToString());
315                 if (state == LeaseState.Initial || state == LeaseState.Expired)
316                     return;            
317                 AddTime(renewOnCallTime);
318             }
319         }
320
321         [System.Security.SecurityCritical]  // auto-generated
322         internal void LeaseExpired(DateTime now)
323         {
324             lock(this)
325             {
326                 BCLDebug.Trace("REMOTE","Lease ",id," LeaseExpired state ",((Enum)state).ToString());
327                 if (state == LeaseState.Expired)
328                     return;
329
330                 // There is a small window between the time the leaseManager
331                 // thread examines all the leases and tests for expiry and 
332                 // when an indivisual lease is locked for expiry. The object 
333                 // could get marshal-ed in this time which would reset its lease
334                 // Therefore we check again to see if we should indeed proceed
335                 // with the expire code (using the same value of 'now' as used
336                 // by the leaseManager thread)
337                 if (leaseTime.CompareTo(now) < 0)
338                     ProcessNextSponsor();
339             }
340         }
341
342         internal delegate TimeSpan AsyncRenewal(ILease lease);
343         
344         [System.Security.SecurityCritical]  // auto-generated
345         internal void SponsorCall(ISponsor sponsor)
346         {
347             BCLDebug.Trace("REMOTE","Lease ",id," SponsorCall state ",((Enum)state).ToString());
348             bool exceptionOccurred = false;
349             if (state == LeaseState.Expired)
350                 return;
351
352             lock(sponsorTable)
353             {
354                 try
355                 {
356                     Object sponsorId = GetSponsorId(sponsor);            
357                     sponsorCallThread = Thread.CurrentThread.GetHashCode();
358                     AsyncRenewal ar = new AsyncRenewal(sponsor.Renewal);
359                     SponsorStateInfo sponsorStateInfo = (SponsorStateInfo)sponsorTable[sponsorId];            
360                     sponsorStateInfo.sponsorState = SponsorState.Waiting;
361
362                     // The first parameter should be the lease we are trying to renew.
363                     IAsyncResult iar = ar.BeginInvoke(this, new AsyncCallback(this.SponsorCallback), null);
364                     if ((sponsorStateInfo.sponsorState == SponsorState.Waiting) && (state != LeaseState.Expired))
365                     {
366                         //   Even if we get here, the operation could still complete before
367                         //   we call the the line below. This seems to be a ----.
368                         
369                         // Sponsor could have completed before statement is reached, so only execute
370                         // if the sponsor state is still waiting
371                         leaseManager.RegisterSponsorCall(this, sponsorId, sponsorshipTimeout);
372                     }
373                     sponsorCallThread = 0;
374                 }catch(Exception)
375                 {
376                     // Sponsor not avaiable
377                     exceptionOccurred = true;
378
379                     sponsorCallThread = 0;
380                 }
381             }
382
383             if (exceptionOccurred)
384             {
385                 BCLDebug.Trace("REMOTE","Lease ",id," SponsorCall Sponsor Exception ");
386                 Unregister(sponsor);
387                 ProcessNextSponsor();
388             }
389         }
390
391         [System.Security.SecurityCritical]  // auto-generated
392         internal void SponsorTimeout(Object sponsorId)
393         {
394             lock (this)
395             {
396                 if (!sponsorTable.ContainsKey(sponsorId))
397                     return;
398                 lock(sponsorTable)
399                 {
400                     SponsorStateInfo sponsorStateInfo = (SponsorStateInfo)sponsorTable[sponsorId];
401                     BCLDebug.Trace("REMOTE","Lease ",id," SponsorTimeout  sponsorState ",((Enum)sponsorStateInfo.sponsorState).ToString());
402                     if (sponsorStateInfo.sponsorState == SponsorState.Waiting)
403                     {
404                         Unregister(GetSponsorFromId(sponsorId));
405                         ProcessNextSponsor();
406                     }
407                 }
408             }
409         }
410
411         [System.Security.SecurityCritical]  // auto-generated
412         private void ProcessNextSponsor()
413         {
414             BCLDebug.Trace("REMOTE","Lease ",id," ProcessNextSponsor");
415
416             Object largestSponsor = null;
417             TimeSpan largestRenewalTime = TimeSpan.Zero;
418             
419             
420             lock(sponsorTable)
421             {
422                 IDictionaryEnumerator e = sponsorTable.GetEnumerator();
423                 // Find sponsor with largest previous renewal value
424                 while(e.MoveNext())
425                 {
426                     Object sponsorId = e.Key;
427                     SponsorStateInfo sponsorStateInfo = (SponsorStateInfo)e.Value;
428                     if ((sponsorStateInfo.sponsorState == SponsorState.Initial) && (largestRenewalTime == TimeSpan.Zero))
429                     {
430                         largestRenewalTime = sponsorStateInfo.renewalTime;
431                         largestSponsor = sponsorId;                        
432                     }
433                     else if (sponsorStateInfo.renewalTime > largestRenewalTime)
434                     {
435                         largestRenewalTime = sponsorStateInfo.renewalTime;
436                         largestSponsor = sponsorId;
437                     }
438                 }
439             }
440
441             if (largestSponsor != null)
442                 SponsorCall(GetSponsorFromId(largestSponsor));
443             else
444             {
445                 // No more sponsors to try, Cancel
446                 BCLDebug.Trace("REMOTE","Lease ",id," ProcessNextSponsor no more sponsors");                
447                 Cancel();
448             }
449         }
450
451
452         // This gets called when we explicitly transfer the call back from the 
453         // called function to a threadpool thread.
454         [System.Security.SecurityCritical]  // auto-generated
455         internal void SponsorCallback(Object obj)
456         {
457             SponsorCallback((IAsyncResult)obj);
458         }
459
460         // On another thread
461         [System.Security.SecurityCritical]  // auto-generated
462         internal void SponsorCallback(IAsyncResult iar)
463         {
464             BCLDebug.Trace("REMOTE","Lease ",id," SponsorCallback IAsyncResult ",iar," state ",((Enum)state).ToString());
465             if (state == LeaseState.Expired)
466             {
467                 return;
468             }
469
470             int thisThread = Thread.CurrentThread.GetHashCode();
471             if (thisThread == sponsorCallThread)
472             {
473                 // Looks like something went wrong and the thread that
474                 // did the AsyncRenewal::BeginInvoke is executing the callback
475                 // We will queue the work to the thread pool (otherwise there
476                 // is a possibility of stack overflow if all sponsors are down)
477                 WaitCallback threadFunc = new WaitCallback(this.SponsorCallback);
478                 ThreadPool.QueueUserWorkItem(threadFunc, iar);
479                 return;
480             }
481
482             AsyncResult asyncResult = (AsyncResult)iar;
483             AsyncRenewal ar = (AsyncRenewal)asyncResult.AsyncDelegate;
484             ISponsor sponsor = (ISponsor)ar.Target;
485             SponsorStateInfo sponsorStateInfo = null;
486             if (iar.IsCompleted)
487             {
488                 // Sponsor came back with renewal
489                 BCLDebug.Trace("REMOTE","Lease ",id," SponsorCallback sponsor completed");
490                 bool exceptionOccurred = false;
491                 TimeSpan renewalTime = TimeSpan.Zero;
492                 try
493                 {
494                     renewalTime = (TimeSpan)ar.EndInvoke(iar);
495                 }catch(Exception)
496                 {
497                     // Sponsor not avaiable
498                     exceptionOccurred = true;
499                 }
500                 if (exceptionOccurred)
501                 {
502                     BCLDebug.Trace("REMOTE","Lease ",id," SponsorCallback Sponsor Exception ");
503                     Unregister(sponsor);
504                     ProcessNextSponsor();
505                 }
506                 else
507                 {
508                     Object sponsorId = GetSponsorId(sponsor);
509                     lock(sponsorTable)
510                     {
511                         if (sponsorTable.ContainsKey(sponsorId))
512                         {
513                             sponsorStateInfo = (SponsorStateInfo)sponsorTable[sponsorId];
514                             sponsorStateInfo.sponsorState = SponsorState.Completed;
515                             sponsorStateInfo.renewalTime = renewalTime;
516                         }
517                         else
518                         {
519                             // Sponsor was deleted, possibly from a sponsor time out
520                         }
521                     }
522
523                     if (sponsorStateInfo == null)
524                     {
525                         // Sponsor was deleted
526                         ProcessNextSponsor();
527                     }
528                     else if (sponsorStateInfo.renewalTime == TimeSpan.Zero)
529                     {
530                         BCLDebug.Trace("REMOTE","Lease ",id," SponsorCallback sponsor did not renew ");                                            
531                         Unregister(sponsor);
532                         ProcessNextSponsor();
533                     }
534                     else
535                         RenewInternal(sponsorStateInfo.renewalTime);
536                 }
537             }
538             else
539             {
540                 // Sponsor timed out
541                 // Note time outs should be handled by the LeaseManager
542                 BCLDebug.Trace("REMOTE","Lease ",id," SponsorCallback sponsor did not complete, timed out");
543                 Unregister(sponsor);                    
544                 ProcessNextSponsor();
545             }
546         }
547
548         
549
550         private void AddTime(TimeSpan renewalSpan)
551         {
552             if (state == LeaseState.Expired)
553                 return;
554
555             DateTime now = DateTime.UtcNow;
556             DateTime oldLeaseTime = leaseTime;
557             DateTime renewTime = now.Add(renewalSpan);
558             if (leaseTime.CompareTo(renewTime) < 0)
559             {
560                 leaseManager.ChangedLeaseTime(this, renewTime);
561                 leaseTime = renewTime;
562                 state = LeaseState.Active;
563             }
564             //BCLDebug.Trace("REMOTE","Lease ",id," AddTime renewalSpan ",renewalSpan," current Time ",now," old leaseTime ",oldLeaseTime," new leaseTime ",leaseTime," state ",((Enum)state).ToString());            
565         }
566
567
568
569         [Serializable]
570         internal enum SponsorState
571         {
572             Initial = 0,
573             Waiting = 1,
574             Completed = 2
575         }
576
577         internal sealed class SponsorStateInfo
578         {
579             internal TimeSpan renewalTime;
580             internal SponsorState sponsorState;
581
582             internal SponsorStateInfo(TimeSpan renewalTime, SponsorState sponsorState)
583             {
584                 this.renewalTime = renewalTime;
585                 this.sponsorState = sponsorState;
586             }
587         }
588     }
589
590     internal class LeaseSink : IMessageSink
591     {
592         Lease lease = null;
593         IMessageSink nextSink = null;
594
595         public LeaseSink(Lease lease, IMessageSink nextSink)
596         {
597             this.lease = lease;
598             this.nextSink = nextSink;
599         }
600         
601         //IMessageSink methods
602         [System.Security.SecurityCritical]  // auto-generated
603         public IMessage SyncProcessMessage(IMessage msg)
604         {
605             //BCLDebug.Trace("REMOTE","Lease ",id," SyncProcessMessage");
606             lease.RenewOnCall();
607             return nextSink.SyncProcessMessage(msg);
608         }
609
610         [System.Security.SecurityCritical]  // auto-generated
611         public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
612         {
613             //BCLDebug.Trace("REMOTE","Lease ",id," AsyncProcessMessage");
614             lease.RenewOnCall();
615             return nextSink.AsyncProcessMessage(msg, replySink);        
616         }
617
618         public IMessageSink NextSink
619         {
620             [System.Security.SecurityCritical]  // auto-generated
621             get
622             {
623                 //BCLDebug.Trace("REMOTE","Lease ",id," NextSink");        
624                 return nextSink;
625             }
626         }
627     }
628