Is it safe to change static readonly variables in C# SQLCLR?
Asked Answered
M

1

8

I wrote some code in C# 6.0 .NET 3.5 CLR assembly with safety level = external_access.

Reduced code:

 public static readonly DataTable warnings_table = init_warnings_table();

 public static void set_warning(string msg)
 {
        var row = warnings_table.NewRow();
        row[1] = DateTime.Now;
        row[2] = msg;
                ...
        warnings_table.Rows.Add(row);
 }
 [Microsoft.SqlServer.Server.SqlProcedure]
 public static SqlInt32 wrapper_func(SqlInt32 param)
 { 
   return big_func(Param.Value);
 }

 int big_func(int param)
 { 
   SqlBulkCopy bulkcopy;
   ....
   set_warning("Message");
   ....
   write_warnings(bulkcopy); 
   warnings_table.Clear();
 }

What will be happen with warnings_table if wrapper_func will be called from 2 or more different connections / sessions simultaneously? There are write operations to static field warnings_table. So, I suppose, but am not sure, it will lead to data race-condition here.

In other words:

  1. Are static read-only variables in SQLCLR unique for every sql connection/sql query/transactions or do they share data between different SQLCLR procedures calls?

  2. Is it possible to have painless global state, safe from other SQLCLR procedures calls?

Marenmarena answered 9/12, 2016 at 14:36 Comment(1)
Gut instinct would be to say no - the only global state you should be working in terms of within a database system should be tables. Can't give you chapter and verse on specific documentation at this point though.Penn
S
7

Are static read-only variables in SQLCLR unique for every sql connection/sql query/transactions or do they share data between different SQLCLR procedures calls?

An Assembly residing inside of SQL Server (i.e. SQLCLR) gets loaded into a single App Domain which is shared across all sessions. There can be multiple App Domains since they are specific to each Database and owner (i.e. Authorization) combination. But any one particular SQLCLR object will only reside in one particular Assembly, so all calls to it do share that single instance. This is why all SQLCLR methods need to be static, since they are not per-Session. So yes, static member/class variables are shared, which is why they need to be declared as readonly, else the Assembly needs to have a PERMISSION_SET of UNSAFE if a static class variable is not marked as readonly.

Is it possible to have painless global state, safe from other SQLCLR procedures calls?

No, at least not in any simple, built-in fashion. A static class variable that is a Collection can be thought of as a real table created in tempdb (not the same as a Global Temporary Table -- ##Table). Once the variable is created it will remain and won't disappear once the "session" that created it ends, but it is accessible to all sessions. So if you need to have per-Session separation, then you need to have a property of the static collection be a differentiator -- something to tell each session apart. And you could possibly get the current session_id / @@SPID and use that, as long as you clear out all entries having that same session_id at the beginning of each process since session_id values are re-used by SQL Server. But there is no way to clean up entries for session_id values that are not re-used, unless you do that as a final step at the end of the process (a step that might not be called if the process runs into an exception). So, not impossible, but will take a little but of work and a lot of testing :-).

It should also be understood that this is not specific to SQLCLR Stored Procedures, but affects all methods and objects running in a particular App Domain. Meaning, static class variables are shared with all code that can access the class, which can be SQLCLR Triggers, Functions, and probably also User-Defined Aggregates and User-Defined Types.

PLEASE NOTE, one nuance that you need to be aware of with regards to static variables and how SQL Server manages memory is:

Occasionally SQL Server, if it is running out of physical memory, may decide to unload one or more App Domains. You can see this behavior in the SQL Server error log by searching for "memory pressure" since there will be entries in the form of:

AppDomain X ({database_name}.{owner_name}[runtime].Y) is marked for unload due to memory pressure.

Unloading the App Domain will clear out the static variables. When it gets reloaded upon the next call to the SQLCLR object, the static variables will be created again.

SO, if the static variable is being used to maintain state between calls that is necessary for "proper" operation, then you need to find another way as that data cannot be guaranteed to exist between calls.

If you need per-Session global state, then you need to use SQL Servers built-in mechanisms by executing T-SQL statements using Context Connection = true; as the Connection String. You can use:


For more information on working with SQLCLR in general, please see the series I am writing on this topic on SQL Server Central (free registration is required):

Stairway to SQLCLR

Septuplet answered 9/12, 2016 at 15:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.