A technique called pooling is useful because it allows resources to be shared efficiently between users. It is typically applied to threads or connections to a database. In certain cases, however, it can be the cause of problems that may not be immediately obvious. The article mentions three dangers that can arise when using different poolings. It describes why they occur and suggests solutions. Ignoring them may result in exhaustion of system resources or security risks.
SQL Connecting Pool
The programmer is encouraged from various sources to establish a connection with the database for the shortest possible time. Typically using clause using
:
public static class Database {
public SqlConnection MyDB {
get {
return new SqlConnection("connection string");
}
}
}
using (var conn = Database.MyDB) {
...
}
The connection to the database is closed after leaving the clause. However, ADO.NET uses pooling to connect with the database. Open connections are retained for later use. A new instance of SqlConnection
usually returns an already established connection, only reset (reset is provided by a simple hand-shake).
The default capacity of the pool is one hundred connections. They remain there for quite a long time. If the application is a service working in many threads, it may happen that the pool becomes overloaded with database connections that are held by one application domain at the expense of another. This can be prevented by disabling pooling in the connection string with parameter Pooling=False
, or adjust the time for which the connection is held in the pool with parameter ConnectionLifetime
.
Thread Pool
Creating a new thread is not one of the fastest operations. Therefore, when a thread has finished its work, it is left alive for a while. Then, when another piece of code requests to create a thread, it is given the unterminated thread and takes on its task. This saves time and resources to create new threads. This system is called Thread Pool. The largest number of threads it can hold is as follows:
- 1023 in .NET 4.0 32 bit
- 32768 in .NET 4.0 64 bit
- 250 per CPU core in .NET 3.5
- 25 per CPU core in .NET 2.0
- There was no Thread Pool in .NET 1.0 yet
If a web page in ASP.NET calls web services, it should be asynchronous. If not, the thread that compiles the content of the web page is blocked while the request to the web service is being processed. If there are many HTTP requests to the server, it may happen that all free threads from the Thread Pool are exhausted and new ones can no longer be created due to lack of memory or computing power. However, most threads will just wait for the web service to respond. That’s unnecessary. If the Web page is asynchronous, its origin processing is served by a single thread, which calls the Web service and terminates. When the web service responds, the response is processed by the second thread, which eventually handles the end of the page generation. While waiting for a service response, the thread is not blocked and can work on other tasks.
<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="default.aspx.cs" Inherits="AsynchronousWebParts.Default" Async="true" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Asynchronous Web Parts</title>
</head>
<body>
<form id="form" runat="server">
<asp:GridView ID="grid" runat="server"/>
</form>
</body>
</html>
namespace AsynchronousWebParts {
public partial class Default : System.Web.UI.Page {
SqlConnection conn;
SqlCommand cmd;
public Default() {
Load += new EventHandler(Page_OnLoad);
}
private void Page_OnLoad(object sender, EventArgs e) {
string connStr = "Data Source=|DataDirectory|database.sdf;async=true";
string sql = "SELECT * FROM [Database].[dbo].[Table]";
conn = new SqlConnection(connStr);
cmd = new SqlCommand(sql, conn);
conn.Open(); // launch data request asynchronously using page async task
Page.RegisterAsyncTask(new PageAsyncTask(
new BeginEventHandler(BeginGetData),
new EndEventHandler(FinishGetData),
new EndEventHandler(Timeout),
null, true)
);
}
IAsyncResult BeginGetData(object src, EventArgs e, AsyncCallback cb, object state) {
return cmd.BeginExecuteReader(cb, state);
}
private void FinishGetData(IAsyncResult ar) {
grid.DataSource = cmd.EndExecuteReader(ar);
grid.DataBind();
conn.Close();
}
private void Timeout(IAsyncResult ar) {
if (conn.State == ConnectionState.Open) conn.Close();
// timed out
}
}
}
The page must contain parameter async="true"
. It can then register the asynchronous task using method RegisterAsyncTask
. Using this procedure can prevent a deadlock in the process that serves the entire Application Pool. Keep in mind that even if everything is done synchronously and there are no problems, they can arise if the server providing the web service goes down. Each request suddenly blocks one thread for 30 seconds (depending on how much timeout you have set).
Application Pool
Each ThreadPool is for a single process only. To make the sharing of threads and other resources efficient on web servers, one process can serve several virtual servers. However, in some cases, this also breaks down security measures. A web application on one virtual server can read the data of another server if it shares the same Application Pool with it. Therefore, it is best to have a separate Application Pool for each customer and thus safely isolate their data from each other.