Thread safety advice

G

Guest

My application uses a singleton static class for writing entries to a log
file. The location and name of the log-file is read from web.config each time
an entry is written, but has the current date inserted into its name. For
example, the string:

c:\inetpub\wwwroot\myapp\log.txt

will be changed to:

c:\inetpub\wwwroot\myapp\log_20060216.txt

However, as we have many concurrent users on the site at once, each
generating hundreds of log file entries a minute, we're finding that multiple
threads are calling the same code at the same time and we're getting filename
with multiple date stamps. Ie:

c:\inetpub\wwwroot\myapp\log_20060216_20060216.txt

I'm sure this is to do with the following code not being thread-safe despite
the "lock" critical section statement:

string sLog_File_Path = ConfigurationManager.AppSettings["logfilepath"];

lock (FoLockObject) {
sLog_File_Path = Path.Combine(Path.GetDirectoryName(sLog_File_Path),
Path.GetFileNameWithoutExtension(sLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(sLog_File_Path));
}

Can anyone offer advice on the correct way to protect code that is
vulnerable to threading issues. I'm aware of synchronization objects such as
Mutex's but am unsure how to use them with static classes. A stright forward
example in C# without being too clever would be very handy

Thanks

Ben
 
G

George Ter-Saakov

Is sLog_File_Path local or member variable?
What about FoLockObject?

Unfortunately it' hard to say where is the problem you need to show us full
class.

George.
 
G

Guest

Here's an abbreviated version of the full class.

public class CLog {
private static string FsLog_File_Path = "";
private static object FoLockObject = new object();

private static void RefreshSettings() {
FsLog_File_Path = ConfigurationSettings.AppSettings["logfilepath"];

lock (FoLockObject) {
FsLog_File_Path =
Path.Combine(Path.GetDirectoryName(FsLog_File_Path),
Path.GetFileNameWithoutExtension(FsLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(FsLog_File_Path));
}
}

public static void WriteInfo(string sDescription) {
RefreshSettings();

StreamWriter oStream = null;

try {
if (File.Exists(FsLog_File_Path)) {
oStream = File.AppendText(FsLog_File_Path);
}
else {
oStream = File.CreateText(FsLog_File_Path);
}

oStream.AutoFlush = true;

string sSession = "";
if (HttpContext.Current != null) sSession =
HttpContext.Current.Session.SessionID + " - ";
else sSession = "";


oStream.WriteLine(string.Format("{0} - {1} - {2}{3}",
DateTime.Now.ToString("dd MMM yyy H:mm:ss"), "INFO", sSession,
sDescription));
}
catch (IOException) {

}
finally {
if (oStream != null) oStream.Close();
oStream = null;
}
}
}

George Ter-Saakov said:
Is sLog_File_Path local or member variable?
What about FoLockObject?

Unfortunately it' hard to say where is the problem you need to show us full
class.

George.


Ben Fidge said:
My application uses a singleton static class for writing entries to a log
file. The location and name of the log-file is read from web.config each
time
an entry is written, but has the current date inserted into its name. For
example, the string:

c:\inetpub\wwwroot\myapp\log.txt

will be changed to:

c:\inetpub\wwwroot\myapp\log_20060216.txt

However, as we have many concurrent users on the site at once, each
generating hundreds of log file entries a minute, we're finding that
multiple
threads are calling the same code at the same time and we're getting
filename
with multiple date stamps. Ie:

c:\inetpub\wwwroot\myapp\log_20060216_20060216.txt

I'm sure this is to do with the following code not being thread-safe
despite
the "lock" critical section statement:

string sLog_File_Path = ConfigurationManager.AppSettings["logfilepath"];

lock (FoLockObject) {
sLog_File_Path = Path.Combine(Path.GetDirectoryName(sLog_File_Path),
Path.GetFileNameWithoutExtension(sLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(sLog_File_Path));
}

Can anyone offer advice on the correct way to protect code that is
vulnerable to threading issues. I'm aware of synchronization objects such
as
Mutex's but am unsure how to use them with static classes. A stright
forward
example in C# without being too clever would be very handy

Thanks

Ben
 
G

George Ter-Saakov

You do have a problem in your code.
This line is not thread safe.

FsLog_File_Path = ConfigurationSettings.AppSettings["logfilepath"];

You are modifying the variable while some other thread could have been doing
the code that is in lock {} section.
So move that line inside (but read further)

-------------------------------------------------
I am not sure why your are doing it this way. Because I do not see any gain
in FsLog_File_Path beign global/member variable.
I would rewrite the code to avoid any synchronization

private static void RefreshSettings() {
string sPath =
ConfigurationSettings.AppSettings["logfilepath"];
sPath = Path.Combine(Path.GetDirectoryName(sPath),
Path.GetFileNameWithoutExtension(sPath) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(sPath));
}
}



As you can see all manipulations are made to local variable so you do not
need lock.

------------------------------------------------------------------

The only place where you will need to lock is where you are opening the file
and writing to it. Since that is not thread safe.
And this will fail if you try to open/write into file from multiple threads.

George


Ben Fidge said:
Here's an abbreviated version of the full class.

public class CLog {
private static string FsLog_File_Path = "";
private static object FoLockObject = new object();

private static void RefreshSettings() {
FsLog_File_Path = ConfigurationSettings.AppSettings["logfilepath"];

lock (FoLockObject) {
FsLog_File_Path =
Path.Combine(Path.GetDirectoryName(FsLog_File_Path),
Path.GetFileNameWithoutExtension(FsLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(FsLog_File_Path));
}
}

public static void WriteInfo(string sDescription) {
RefreshSettings();

StreamWriter oStream = null;

try {
if (File.Exists(FsLog_File_Path)) {
oStream = File.AppendText(FsLog_File_Path);
}
else {
oStream = File.CreateText(FsLog_File_Path);
}

oStream.AutoFlush = true;

string sSession = "";
if (HttpContext.Current != null) sSession =
HttpContext.Current.Session.SessionID + " - ";
else sSession = "";


oStream.WriteLine(string.Format("{0} - {1} - {2}{3}",
DateTime.Now.ToString("dd MMM yyy H:mm:ss"), "INFO", sSession,
sDescription));
}
catch (IOException) {

}
finally {
if (oStream != null) oStream.Close();
oStream = null;
}
}
}

George Ter-Saakov said:
Is sLog_File_Path local or member variable?
What about FoLockObject?

Unfortunately it' hard to say where is the problem you need to show us
full
class.

George.


Ben Fidge said:
My application uses a singleton static class for writing entries to a
log
file. The location and name of the log-file is read from web.config
each
time
an entry is written, but has the current date inserted into its name.
For
example, the string:

c:\inetpub\wwwroot\myapp\log.txt

will be changed to:

c:\inetpub\wwwroot\myapp\log_20060216.txt

However, as we have many concurrent users on the site at once, each
generating hundreds of log file entries a minute, we're finding that
multiple
threads are calling the same code at the same time and we're getting
filename
with multiple date stamps. Ie:

c:\inetpub\wwwroot\myapp\log_20060216_20060216.txt

I'm sure this is to do with the following code not being thread-safe
despite
the "lock" critical section statement:

string sLog_File_Path =
ConfigurationManager.AppSettings["logfilepath"];

lock (FoLockObject) {
sLog_File_Path = Path.Combine(Path.GetDirectoryName(sLog_File_Path),
Path.GetFileNameWithoutExtension(sLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(sLog_File_Path));
}

Can anyone offer advice on the correct way to protect code that is
vulnerable to threading issues. I'm aware of synchronization objects
such
as
Mutex's but am unsure how to use them with static classes. A stright
forward
example in C# without being too clever would be very handy

Thanks

Ben
 
B

Ben Fidge

Hi George,

I see the error of my ways and have changed it as you suggested. This was
basically a quick hack to include the date in the filename, as this wasn't
the origianl intention.

Thanks for the advice

Ben


George Ter-Saakov said:
You do have a problem in your code.
This line is not thread safe.

FsLog_File_Path = ConfigurationSettings.AppSettings["logfilepath"];

You are modifying the variable while some other thread could have been
doing the code that is in lock {} section.
So move that line inside (but read further)

-------------------------------------------------
I am not sure why your are doing it this way. Because I do not see any
gain in FsLog_File_Path beign global/member variable.
I would rewrite the code to avoid any synchronization

private static void RefreshSettings() {
string sPath =
ConfigurationSettings.AppSettings["logfilepath"];
sPath = Path.Combine(Path.GetDirectoryName(sPath),
Path.GetFileNameWithoutExtension(sPath) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(sPath));
}
}



As you can see all manipulations are made to local variable so you do not
need lock.

------------------------------------------------------------------

The only place where you will need to lock is where you are opening the
file and writing to it. Since that is not thread safe.
And this will fail if you try to open/write into file from multiple
threads.

George


Ben Fidge said:
Here's an abbreviated version of the full class.

public class CLog {
private static string FsLog_File_Path = "";
private static object FoLockObject = new object();

private static void RefreshSettings() {
FsLog_File_Path =
ConfigurationSettings.AppSettings["logfilepath"];

lock (FoLockObject) {
FsLog_File_Path =
Path.Combine(Path.GetDirectoryName(FsLog_File_Path),
Path.GetFileNameWithoutExtension(FsLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(FsLog_File_Path));
}
}

public static void WriteInfo(string sDescription) {
RefreshSettings();

StreamWriter oStream = null;

try {
if (File.Exists(FsLog_File_Path)) {
oStream = File.AppendText(FsLog_File_Path);
}
else {
oStream = File.CreateText(FsLog_File_Path);
}

oStream.AutoFlush = true;

string sSession = "";
if (HttpContext.Current != null) sSession =
HttpContext.Current.Session.SessionID + " - ";
else sSession = "";


oStream.WriteLine(string.Format("{0} - {1} - {2}{3}",
DateTime.Now.ToString("dd MMM yyy H:mm:ss"), "INFO", sSession,
sDescription));
}
catch (IOException) {

}
finally {
if (oStream != null) oStream.Close();
oStream = null;
}
}
}

George Ter-Saakov said:
Is sLog_File_Path local or member variable?
What about FoLockObject?

Unfortunately it' hard to say where is the problem you need to show us
full
class.

George.


My application uses a singleton static class for writing entries to a
log
file. The location and name of the log-file is read from web.config
each
time
an entry is written, but has the current date inserted into its name.
For
example, the string:

c:\inetpub\wwwroot\myapp\log.txt

will be changed to:

c:\inetpub\wwwroot\myapp\log_20060216.txt

However, as we have many concurrent users on the site at once, each
generating hundreds of log file entries a minute, we're finding that
multiple
threads are calling the same code at the same time and we're getting
filename
with multiple date stamps. Ie:

c:\inetpub\wwwroot\myapp\log_20060216_20060216.txt

I'm sure this is to do with the following code not being thread-safe
despite
the "lock" critical section statement:

string sLog_File_Path =
ConfigurationManager.AppSettings["logfilepath"];

lock (FoLockObject) {
sLog_File_Path = Path.Combine(Path.GetDirectoryName(sLog_File_Path),
Path.GetFileNameWithoutExtension(sLog_File_Path) + "_" +
DateTime.Now.ToString("yyyyMMdd") +
Path.GetExtension(sLog_File_Path));
}

Can anyone offer advice on the correct way to protect code that is
vulnerable to threading issues. I'm aware of synchronization objects
such
as
Mutex's but am unsure how to use them with static classes. A stright
forward
example in C# without being too clever would be very handy

Thanks

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top