Столкнулся с очень непонятной проблемой...
При работе написанного мной приложения (.NET C#) начинает течь память, но не в самом приложении, а в системном процессе csrss.exe (!)...
Приложение работает как сервис и постоянно опрашивает удаленные сервера, читает с них файлы следующим способом: монтирует, например, шару "C$" на свой сетевой диск, читает нужные файлы, затем отмонтирует. Все это работает в нескольких параллельных потоках (5 потоков), поскольку серверов много (около 150) и опрос идет каждые 5 минут.
Методом комментирования всего по порядку было выяснено, что память начинает течь именно в функции монтирования (или размонтирования) дисков.
После плясок с бубном и попыток обнаружить неосвобождаемые ресурсы я переписал эту функцию с использованием другого (другой библиотеки) способа - через прямой вызов WinAPI.
К сожалению, это не помогло и утекать оно не перестало..
Привожу ниже оба примера для наглядности.
Первый - с использованием Windows Script Host (IWshRuntimeLibrary) - "вредный" вызов обозначил "// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
using System.IO;
using IWshRuntimeLibrary;
// ...
namespace Test1
{
class Test1
{
// ...
private static System.Collections.Generic.List<char> mountDrives=new List<char>(); // Will contain drive letters that mounted by this appication
private const string listMountLetters = "GHIJKLMNOPQRSTUVWXYZ"; // We can use these drives to mount on remote PCs
public char MountDrive(string server, string relpath, string user, string pass)
{
logger.Debug("MountDrive start. server='" + server + "', path='" + relpath + "'");
char driveLetter = ' ';
const int MOUNTRETRYMAXCOUNT = 10; // Try some times (wait if anyone free drive letter)
WshNetworkClass owNetwork = null;
try
{
owNetwork = new WshNetworkClass();
for (int retryc = 0; retryc < MOUNTRETRYMAXCOUNT; retryc++)
{
foreach (char curd in listMountLetters)
{
if (mountDrives.Contains(curd)) continue; // We're already using this drive
try
{
DriveType dt = DriveType.Unknown;
foreach (System.IO.DriveInfo di in System.IO.DriveInfo.GetDrives())
{
if (di.Name == curd + ":\\")
{
dt = di.DriveType;
break;
}
}
if (dt != DriveType.Unknown) continue;
driveLetter = curd;
WshNetworkClass owNetwork = new WshNetworkClass();
object obj1 = false;
object ouser = server + "\\" + user, opass = pass;
if (user.Contains("\\"))
ouser = user; // domain user - do not concatenate with server's name
owNetwork.MapNetworkDrive(curd + ":", "\\\\" + server + "\\" + relpath, ref obj1, ref ouser, ref opass); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
mountDrives.Add(driveLetter);
break;
}
catch (System.Runtime.InteropServices.COMException cex)
{
driveLetter = ' ';
switch ((uint)cex.ErrorCode)
{
case 0x800704B3:
logger.Error("Server '" + server + "' or its shared folder '" + relpath + "' is not available !");
return ' ';
case 0x8007052E:
logger.Error("User or password is wrong for server '" + server + "'!");
return ' ';
case 0x800704C3:
logger.Error("Multiple connections to a server '" + server + "' or shared resource by the same user!");
return ' ';
case 0x8007089A:
logger.Error("The specified username is invalid for server '" + server + "'!");
return ' ';
default:
//
break;
}
logger.Debug("MountDrive cex: " + cex.ToString());
}
catch (Exception ex)
{
logger.Debug("MountDrive: Exception : " + ex.ToString());
driveLetter = ' ';
continue;
}
}
if (driveLetter != ' ') break;
else if (retryc < MOUNTRETRYMAXCOUNT - 1)
System.Threading.Thread.Sleep(3000); // Sleep before next retry
}
}
finally
{
if (owNetwork != null)
if (System.Runtime.InteropServices.Marshal.IsComObject(owNetwork))
System.Runtime.InteropServices.Marshal.ReleaseComObject(owNetwork);
}
logger.Debug("MountDrive end. driveLetter='" + driveLetter + "'");
return driveLetter;
}
public bool UnMountDrive(char letter)
{
logger.Debug("UnMountDrive start. letter='" + letter + "'");
WshNetworkClass owNetwork = null;
try
{
owNetwork = new WshNetworkClass();
object obj1 = true, obj2 = false;
owNetwork.RemoveNetworkDrive(letter + ":", ref obj1, ref obj2); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
mountDrives.Remove(letter);
}
catch (Exception ex)
{
logger.Error("UnMountDrive('" + letter + "'): Exception: " + ex.ToString());
}
finally
{
if (owNetwork != null)
if (System.Runtime.InteropServices.Marshal.IsComObject(owNetwork))
System.Runtime.InteropServices.Marshal.ReleaseComObject(owNetwork);
}
logger.Debug("UnMountDrive end");
return true;
}
}
}
Второй - с использованием Win32API: WNetAddConnection2:
using System.IO;
using System.Runtime.InteropServices;
// ...
namespace Test1
{
class Test1
{
// ...
private static System.Collections.Generic.List<char> mountDrives=new List<char>(); // Will contain drive letters that mounted by this appication
private const string listMountLetters = "GHIJKLMNOPQRSTUVWXYZ"; // We can use these drives to mount on remote PCs
[StructLayout(LayoutKind.Sequential)]
public struct NETRESOURCEA
{
public int dwScope;
public int dwType;
public int dwDisplayType;
public int dwUsage;
[MarshalAs(UnmanagedType.LPStr)]
public string lpLocalName;
[MarshalAs(UnmanagedType.LPStr)]
public string lpRemoteName;
[MarshalAs(UnmanagedType.LPStr)]
public string lpComment;
[MarshalAs(UnmanagedType.LPStr)]
public string lpProvider;
}
[DllImport("mpr.dll", CharSet = CharSet.Auto)]
public static extern int WNetAddConnection2A(
[MarshalAs(UnmanagedType.LPArray)] NETRESOURCEA[] lpNetResource,
[MarshalAs(UnmanagedType.LPStr)] string lpPassword,
[MarshalAs(UnmanagedType.LPStr)] string UserName,
int dwFlags);
[DllImport("mpr.dll", CharSet = CharSet.Auto)]
public static extern int WNetCancelConnection2A(
[MarshalAs(UnmanagedType.LPStr)] string lpName,
int dwFlags,
int fForce);
public char MountDrive(string server, string relpath, string user, string pass)
{
logger.Debug("MountDrive start. server='" + server + "', path='" + relpath + "'");
char driveLetter = ' ';
const int MOUNTRETRYMAXCOUNT = 10; // Try some times (wait if anyone free drive letter)
for (int retryc = 0; retryc < MOUNTRETRYMAXCOUNT; retryc++)
{
foreach (char curd in listMountLetters)
{
if (mountDrives.Contains(curd)) continue; // We're already using this drive
try
{
NETRESOURCEA[] n = new NETRESOURCEA[1];
n[0] = new NETRESOURCEA();
n[0].dwType = 1;
int dwFlags = 4;
n[0].lpLocalName = curd + ":";
n[0].lpRemoteName = "\\\\" + server + "\\" + relpath;
n[0].lpProvider = null;
int res = WNetAddConnection2A(n, pass, user, dwFlags); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
logger.Info("MountDrive, code=" + res.ToString());
if (res != 0) throw new Exception("error mount, code=" + res.ToString());
driveLetter = curd;
mountDrives.Add(driveLetter);
break;
}
catch (Exception mex)
{
logger.Debug("MountDrive: Exception : " + mex.ToString());
driveLetter = ' ';
continue;
}
}
if (driveLetter != ' ') break;
else if (retryc < MOUNTRETRYMAXCOUNT - 1)
System.Threading.Thread.Sleep(3000); // Sleep before next retry
}
logger.Debug("MountDrive end. driveLetter='" + driveLetter + "'");
return driveLetter;
}
public bool UnMountDrive(char letter)
{
logger.Debug("UnMountDrive start. letter='" + letter + "'");
try
{
int rv = WNetCancelConnection2A(letter + ":", 0, 1); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
mountDrives.Remove(letter);
}
catch (Exception ex)
{
logger.Error("UnMountDrive('" + letter + "'): Exception: " + ex.ToString());
}
logger.Debug("UnMountDrive end");
return true;
}
Память утекает здорово - около 2 Gb (!) за 3 дня
Поскольку она течет в системном процессе - помогает только ребут всего сервера, что очень напрягает.
ЧТО я делаю неправильно? Что нужно освобождать еще?
Как вариант, можно отказаться от монтирования диска и читать файлы напрямую через UNC-путь, но тогда нужно каким-то образом имперсонировать юзера, под которым соединяться с сервером.
Добавлено через 3 минуты и 8 секунд:Вот скриншот для примера: