@@ -768,41 +768,61 @@ private async Task<IDisposable> WatchInternal<T>(
768768 Func < string , object ? [ ] ? , Task < T [ ] > > getter
769769 )
770770 {
771- try
772- {
773- var resolvedTables = await ResolveTables ( query , parameters , options ) ;
774- var result = await getter ( query , parameters ) ;
775- handler . OnResult ( result ) ;
771+ var subscription = new WatchSubscription ( ) ;
776772
777- var subscription = OnChange ( new WatchOnChangeHandler
773+ async Task ResetQuery ( )
774+ {
775+ try
778776 {
779- OnChange = async ( change ) =>
777+ var resolvedTables = await ResolveTables ( query , parameters , options ) ;
778+ var result = await getter ( query , parameters ) ;
779+ handler . OnResult ( result ) ;
780+
781+ var onChangeListener = OnChange ( new WatchOnChangeHandler
780782 {
781- try
783+ OnChange = async ( change ) =>
782784 {
783- var result = await getter ( query , parameters ) ;
784- handler . OnResult ( result ) ;
785- }
786- catch ( Exception ex )
787- {
788- handler . OnError ? . Invoke ( ex ) ;
789- }
790- } ,
791- OnError = handler . OnError
792- } , new SQLWatchOptions
793- {
794- Tables = resolvedTables ,
795- Signal = options ? . Signal ,
796- ThrottleMs = options ? . ThrottleMs
797- } ) ;
785+ try
786+ {
787+ var result = await getter ( query , parameters ) ;
788+ handler . OnResult ( result ) ;
789+ }
790+ catch ( Exception ex )
791+ {
792+ handler . OnError ? . Invoke ( ex ) ;
793+ }
794+ } ,
795+ OnError = handler . OnError
796+ } , new SQLWatchOptions
797+ {
798+ Tables = resolvedTables ,
799+ Signal = options ? . Signal ,
800+ ThrottleMs = options ? . ThrottleMs
801+ } ) ;
798802
799- return subscription ;
803+ subscription . SetOnChangeListener ( onChangeListener ) ;
804+ }
805+ catch ( Exception ex )
806+ {
807+ handler . OnError ? . Invoke ( ex ) ;
808+ throw ;
809+ }
800810 }
801- catch ( Exception ex )
811+
812+ // Register initial subscription
813+ await ResetQuery ( ) ;
814+
815+ // Listen for schema changes and reset listener
816+ var schemaListener = RunListener ( async ( e ) =>
802817 {
803- handler . OnError ? . Invoke ( ex ) ;
804- throw ;
805- }
818+ if ( e . SchemaChanged != null )
819+ {
820+ await ResetQuery ( ) ;
821+ }
822+ } ) ;
823+ subscription . SetSchemaListener ( schemaListener ) ;
824+
825+ return subscription ;
806826 }
807827
808828 private class ExplainedResult
@@ -869,7 +889,7 @@ void flushTableUpdates()
869889 } ) ;
870890 }
871891
872- var cts = Database . RunListener ( ( update ) =>
892+ var dbListenerCts = Database . RunListener ( ( update ) =>
873893 {
874894 if ( update . TablesUpdated != null )
875895 {
@@ -885,27 +905,29 @@ void flushTableUpdates()
885905 }
886906 } ) ;
887907
888- CancellationTokenSource linkedCts ;
889- if ( options ? . Signal . HasValue == true )
908+ CancellationTokenSource stopRunningCts ;
909+
910+ if ( options ? . Signal != null )
890911 {
891- // Cancel on global CTS cancellation or user token cancellation
892- linkedCts = CancellationTokenSource . CreateLinkedTokenSource (
912+ var linkedCts = CancellationTokenSource . CreateLinkedTokenSource (
893913 watchSubscriptionCts . Token ,
894914 options . Signal . Value
895915 ) ;
916+ stopRunningCts = linkedCts ;
896917 }
897918 else
898919 {
899- // Cancel on global CTS cancellation
900- linkedCts = watchSubscriptionCts ;
920+ stopRunningCts = watchSubscriptionCts ;
901921 }
902922
903- var registration = linkedCts . Token . Register ( ( ) =>
923+ var stopRunningReg = stopRunningCts . Token . Register ( dbListenerCts . Cancel ) ;
924+
925+ return new ActionDisposable ( ( ) =>
904926 {
905- cts . Cancel ( ) ;
927+ stopRunningReg . Dispose ( ) ;
928+ dbListenerCts . Cancel ( ) ;
929+ dbListenerCts . Dispose ( ) ;
906930 } ) ;
907-
908- return new WatchSubscription ( cts , registration ) ;
909931 }
910932
911933 private static void HandleTableChanges ( HashSet < string > changedTables , HashSet < string > watchedTables , Action < string [ ] > onDetectedChanges )
@@ -968,21 +990,63 @@ public class WatchOnChangeHandler
968990 public Action < Exception > ? OnError { get ; set ; }
969991}
970992
971- public class WatchSubscription ( CancellationTokenSource cts , CancellationTokenRegistration registration ) : IDisposable
993+ public class WatchSubscription : IDisposable
972994{
973- private readonly CancellationTokenSource _cts = cts ;
974- private readonly CancellationTokenRegistration _registration = registration ;
995+ private IDisposable ? _onChangeListener ;
996+ private IDisposable ? _schemaListener ;
997+ private readonly object _lock = new ( ) ;
975998 private bool _disposed ;
976999
977- public bool Disposed { get { return _disposed ; } }
1000+ internal void SetSchemaListener ( IDisposable listener )
1001+ {
1002+ lock ( _lock )
1003+ {
1004+ if ( _disposed )
1005+ {
1006+ listener . Dispose ( ) ;
1007+ return ;
1008+ }
1009+ _schemaListener ? . Dispose ( ) ;
1010+ _schemaListener = listener ;
1011+ }
1012+ }
1013+
1014+ internal void SetOnChangeListener ( IDisposable listener )
1015+ {
1016+ lock ( _lock )
1017+ {
1018+ if ( _disposed )
1019+ {
1020+ listener . Dispose ( ) ;
1021+ return ;
1022+ }
1023+ _onChangeListener ? . Dispose ( ) ;
1024+ _onChangeListener = listener ;
1025+ }
1026+ }
1027+
1028+ public void Dispose ( )
1029+ {
1030+ lock ( _lock )
1031+ {
1032+ if ( _disposed ) return ;
1033+ _disposed = true ;
1034+
1035+ _onChangeListener ? . Dispose ( ) ;
1036+ _schemaListener ? . Dispose ( ) ;
1037+ }
1038+ }
1039+ }
1040+
1041+ public class ActionDisposable ( Action onDispose ) : IDisposable
1042+ {
1043+ private readonly Action _onDispose = onDispose ;
1044+ private bool _disposed = false ;
9781045
9791046 public void Dispose ( )
9801047 {
9811048 if ( _disposed ) return ;
9821049 _disposed = true ;
983-
984- _registration . Dispose ( ) ;
985- _cts . Cancel ( ) ;
986- _cts . Dispose ( ) ;
1050+ _onDispose ( ) ;
9871051 }
9881052}
0 commit comments