Skip to content

[WIP] Async generator #588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 91 commits into from
Closed

Conversation

hazzik
Copy link
Member

@hazzik hazzik commented Mar 31, 2017

Some things left on the NHibernate side:

  • We need to define how the cancellation token parameter will be generated (current proposal)
  • Some warnings are produced because of the <include /> tag (example), why not use <inheritdoc /> tag instead?
  • Create a tool for generating the code (what options should the tool have?)
  • Add missing ConfigureAwait(false) for the manually written code
  • Replace [ThreadStatic] with AsyncLocal<> on the SessionIdLoggingContext
  • Add missing CancellationToken parameters to manually written async methods (linq extensions and future)
  • Fix broken tests for Firebird
  • Fix broken tests for Oracle
  • Fix broken tests for PostgreSQL (Fix compatibility issue with AssertionException nunit/nunit#2046)
  • Fix broken tests for SqlServer 2012 (NH-4022)
  • Fix broken tests for MySQL

@hazzik hazzik changed the title Async generator [WIP] Async generator Apr 6, 2017
@hazzik hazzik force-pushed the AsyncGenerator branch from 0bcb6c0 to 38d9606 Compare May 4, 2017 11:54

private async Task<IList<TRoot>> ListAsync()
{
return await (criteria.ListAsync<TRoot>()).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maca88 I think we do not need to await here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the less we await, the better. Everywhere the task may just get passed along without awaiting, it should be passed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, it seems that the "algorithm" to check if the returned type from the async method matches the method return type fails here. Will check and fix after I get home.

Copy link
Member

@fredericDelaporte fredericDelaporte May 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When returning the Task without awaiting it, remove async keyword.

Edit: Ok, I have missed completely the point, sorry.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot, @maca88

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asked on SO how to implement this: http://stackoverflow.com/q/43792577/259946

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats great! I am really curious what will be the answser :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there would be no answers:(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This other question is similar.

In the case of an API having some not implemented members, for me it does not make much sens to delay the exception to the next await by putting it in a task. I would rather have it fail as early as possible.

(I would tend to do that for any cases indeed. If we fail to build the task, should we really emulate an async failure instead of letting the exception flow synchronously? I am thinking about this. Personally I would not put all those try catch for converting exceptions to failed task.)

@hazzik
Copy link
Member Author

hazzik commented May 6, 2017

One of the major concerns about async NHibernte is that Session is not thread-safe....

@maca88
Copy link
Contributor

maca88 commented May 6, 2017

That is not a problem when using the async/await (without ConfigureAwait(false)) as by default the code is always executing in the same thread. For using ConfigureAwait(false) we need know if the ISession or other types reiles on the thread context, if not then we can safely use it. Also the async generator is not putting any Task.WhenAll call, as Task.WhenAll should never be used if the passed types are not thread safe. So in other words, if the synchronously code takes care of the thread-safe issue then automatically the async generated code will take care of it (the exception is when using the ConfigureAwait(false) option).

@fredericDelaporte
Copy link
Member

fredericDelaporte commented May 6, 2017

The session itself does not rely on thread context, but the session factory may through the ThreadStaticSessionContext and the ThreadLocalSessionContext (dating back to 2008 and using [ThreadStatic], not ThreadLocal).

The user should be aware that using such session contextes with async await is an error, so I do not think it would be an issue. But we need to provide an AsyncLocalSessionContext I think.

@fredericDelaporte
Copy link
Member

fredericDelaporte commented May 6, 2017

Checking the code, there are some other usages of [ThreadStatic]:

  • The Table class, looks used in session factory building, so it should not be an issue, no async await expected there.
  • The DelayedPostInsertIdentifier, investigations required.
  • SessionIdLoggingContext: if its usage wrap some async call, will mix ids of sessions in the logs.

@fredericDelaporte
Copy link
Member

fredericDelaporte commented May 6, 2017

Other subject: will NHibernateUtil.Initialize(entityOrCollectionProxy) have an async version? The interfaces it uses seems to have their async method already generated.

@maca88
Copy link
Contributor

maca88 commented May 6, 2017

About session context classes, I agree with you that we should provide an AsyncLocalSessionContext.
About [ThreadStatic] usages, the only type that is preventing us at the moment to have the ConfigureAwait(false) option is SessionIdLoggingContext. DelayedPostInsertIdentifier is never used in code as the only usage of is never reached because of this line.

Currently the async version of NHibernateUtil.Initialize is generated, but after seeing the generated method I discovered a "bug" as the current generated method uses awaits instead of just returning the task, thanks for pointing it out, I will fix this. :)

@maca88
Copy link
Contributor

maca88 commented May 6, 2017

Fixed the generation of the NHibernateUtil.Initialize and many other methods to omit the async/await keywords.

@maca88
Copy link
Contributor

maca88 commented May 7, 2017

By looking at the log that the generator produces, there are few members that we could refactor in order to make them async:
NHibernate.Engine.Query.HQLQueryPlan.DoIterate - Remove out parameters
NHibernate.Impl.SessionFactoryImpl.SessionFactoryImpl - Replace with a factory method
NHibernate.Tool.hbm2ddl.DatabaseMetadata.DatabaseMetadata - Replace with a factory method

@hazzik
Copy link
Member Author

hazzik commented May 7, 2017

DatabaseMetadata & SessionFactoryImpl are not really necessary to be made async as they meant to be singletons or created only once.

HQLQueryPlan is worth looking.

int rowsAffected;
try
{
rowsAffected = currentBatch.ExecuteNonQuery();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is strange...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is fine. CurrentBatch does not have async version of ExecuteNonQuery

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit unfortunate though, that looks to me as the main IO of flushes. But current concrete batcher implementations, be it System.Data.SqlClient.SqlCommandSet or MySql.Data.MySqlClient.MySqlDataAdapter do not have async methods for this. Now if someday they start having it, the generator will not be able to catch them since we currently interfacing with those types by reflection.

@cremor
Copy link
Contributor

cremor commented May 13, 2017

One of the major concerns about async NHibernte is that Session is not thread-safe....

That is not a problem when using the async/await (without ConfigureAwait(false)) as by default the code is always executing in the same thread.

This is not correct. There are situations where the continuation code is not executed on the same thread, even if you don't call ConfigureAwait(false):

  1. The code might run in an application that doesn't use a SynchronizationContext (e.g. console application, windows service, unit test).
  2. Someone higher up in the call stack might have called ConfigureAwait(false) (on a method that didn't execute synchronously).

But all libraries should always call ConfigureAwait(false) on each await call anyway. Seems seems like this is done correctly in this pull request.

@maca88
Copy link
Contributor

maca88 commented May 13, 2017

The code might run in an application that doesn't use a SynchronizationContext (e.g. console application, windows service, unit test).

You are right, thanks to pointing it out as I completely forgot about this.

To recap about the thread safe concern, ISession is not thread safe mainly because:

  • May hold a DBConnection which is not thread safe
  • Entity states are stored in a non thread safe collections (Dictionary<,>)

To avoid having problem with thread safety in async code we must:

  • Never start a new task using a session if a previous task involving the session is not ended: await the previous one before launching the new one. Do not run tasks using the same session in parallel.
  • Replace the ThreadLocal<> or ThreadStaticAttribute with AsyncLocal<> where needed so the value is preserved with the async flow, there should be no consequences for the sync code.
  • Avoid using Task.WhenAll or Task.WhenAny when a non thread safe instance is involved.

As we agree, we should use ConfigureAwait(false) on each await to gain performace, also this shouldn't produce any negative effect on the DBConnection or any other non thread safe object as long the await keyword is used.
I mentioned DBConnection because I found that Entity Framework Core does not use the ConfigureAwait(false) option and I didn't found any explanation why.
If anyone have an explanation why, please enlighten me :)

@cremor
Copy link
Contributor

cremor commented May 14, 2017

I mentioned DBConnection because I found that Entity Framework Core does not use the ConfigureAwait(false) option and I didn't found any explanation why.
If anyone have an explanation why, please enlighten me :)

You can find a fairly recent discussion about this in dotnet/efcore#2108 (comment). Even if EF Core doesn't use ConfigureAwait(false), I personally think NHibernate should do it anyway. It not only helps performance but can also avoids deadlocks. Async/Await - Best Practices in Asynchronous Programming is an interesting read about that topic. I'd even suggest to use a code analyzer to make sure that each await call specifies ConfigureAwait(false).

One more point about thread safety: Just like EF6 and EF Core have it documented, NHibernate should then also warn its users to not run multiple async calls in parallel. If possible, it should also detect this and throw an exception like EF6 does. Although it seems like this detection did not make it in EF Core, as can be seen by the not very helpful exceptions that are thrown in this case according to this Stack Overflow question: http://stackoverflow.com/questions/43496608/avoid-entity-framework-error-with-multiple-tasks-running-concurrently-on-same-db

@fredericDelaporte
Copy link
Member

fredericDelaporte commented May 14, 2017

Excuse me if my question is due to lack of knowledge about async/await, but when you prescribe:

  • Always use the await keyword on a returned task in order to avoid having multiple tasks running simultaneously.

Do you mean we should revert the change about directly returning Task wherever possible instead of awaiting them? Or do you just mean that when we have multiple tasks to await for performing a job, we must await them sequentially, instead of using multiple awaiting calls (WhenAll, WhenAny)? If it is the later, I would rather have the previous sentence reworded. Something like:

  • Never use a multiple awaiting call on tasks using the same session, always await them in turn sequentially.

@cremor
Copy link
Contributor

cremor commented May 14, 2017

It's the latter. Here is the wording from EF Core:

EF Core does not support multiple parallel operations being run on the same context instance. You should always wait for an operation to complete before beginning the next operation. This is typically done by using the await keyword on each asynchronous operation.

The former point, directly returning a Task when await is not needed, is fine and even considered best practice.

@maca88
Copy link
Contributor

maca88 commented May 14, 2017

Always use the await keyword on a returned task in order to avoid having multiple tasks running simultaneously.

An example of what I meant:

public async Task DoSomething(ISession session) {
	session.SaveAsync(new Person("Bob")); // await is missing on the returned task
	session.SaveAsync(new Person("Ann")); // await is missing on the returned task
}

@fredericDelaporte
Copy link
Member

So to cover all cases while not excluding valid ones:

  • Never start a new task using a session if a previous task involving the session is not ended: await the previous one before launching the new one. Do not run tasks using the same session in parallel.

@cremor
Copy link
Contributor

cremor commented May 14, 2017

I like the wording of EF Core more. You focus on parallel running tasks, but the second operation doesn't have to be async to cause threading problems.

@maca88
Copy link
Contributor

maca88 commented May 25, 2017

It took me some time but finally the generator is now able to generate the async code for the test project. @hazzik please re-generate :)
The only thing that the generator is not able to generate is the conversion of the Future to FutureAsync method as the return type differs from the sync one.
So if we want to have async tests for the FutureAsync method we will need to write them manually until the generator will be able to do it.

Beside that there are some things left on the NHibernate side (moved to first message).

@fredericDelaporte
Copy link
Member

fredericDelaporte commented May 25, 2017

Alexander will likely not regenerate before June. And I have not yet looked at what should be done for doing it myself.

@hazzik
Copy link
Member Author

hazzik commented May 30, 2017

@maca88 I did regenerate the project (did not push it yet). For some reason the generator inserts "CancellationToken.None" to every method call in tests, even if the method has a default argument.

For example:

[Test]
public async Task FlushProcessingAsync()
{
	//http://opensource.atlassian.com/projects/hibernate/browse/HHH-1663
	ISession session = OpenSession();
	session.BeginTransaction();
	Person person = new Person();
	Address address = new Address();
	person.Data = address;
	await (session.SaveOrUpdateAsync(person, CancellationToken.None));
	await (session.SaveOrUpdateAsync(address, CancellationToken.None));
	await (session.Transaction.CommitAsync(CancellationToken.None));
	session.Close();

	session = OpenSession();
	session.BeginTransaction();
	person = (Person) await (session.LoadAsync(typeof (Person), person.Id, CancellationToken.None));
	person.Name = "makingpersondirty";
	await (session.Transaction.CommitAsync(CancellationToken.None));
	session.Close();

	session = OpenSession();
	session.BeginTransaction();
	await (session.DeleteAsync(person, CancellationToken.None));
	await (session.DeleteAsync(address, CancellationToken.None));
	await (session.Transaction.CommitAsync(CancellationToken.None));
	session.Close();
}

None of these CancellationToken.None are required.

@hazzik
Copy link
Member Author

hazzik commented May 30, 2017

Re: my last comment. There are more then a 10,000 calls with CancellationToken.None, instead of about 100 (76 to be precise)

@maca88
Copy link
Contributor

maca88 commented May 30, 2017

For some reason the generator inserts "CancellationToken.None" to every method call in tests, even if the method has a default argument.

Fixed.

There are more then a 10,000 calls with CancellationToken.None, instead of about 100 (76 to be precise)

Strange, with the above fix now we have 117 usages of CancellationToken.None and after a quick look, all seem to be required.

@hazzik
Copy link
Member Author

hazzik commented May 30, 2017

now we have 117 usages

Good enough for me:)

@hazzik
Copy link
Member Author

hazzik commented May 30, 2017

after a quick look, all seem to be required.

Not really, here is an example, where they are still not required:

[Test]
public async Task CurrentTimestampSelectionAsync()
{
	var conf = TestConfigurationHelper.GetDefaultConfiguration();
	Dialect.Dialect dialect = Dialect.Dialect.GetDialect(conf.Properties);
	if (!dialect.SupportsCurrentTimestampSelection)
	{
		Assert.Ignore("This test does not apply to " + dialect.GetType().FullName);
	}
	var sessions = (ISessionFactoryImplementor)conf.BuildSessionFactory();
	sessions.ConnectionProvider.Configure(conf.Properties);
	IDriver driver = sessions.ConnectionProvider.Driver;

	using (var connection = await (sessions.ConnectionProvider.GetConnectionAsync(CancellationToken.None))) // Required
	{
		var statement = driver.GenerateCommand(CommandType.Text, new SqlString(dialect.CurrentTimestampSelectString), new SqlType[0]);
		statement.Connection = connection;
		using (var reader = await (statement.ExecuteReaderAsync(CancellationToken.None))) //Optional
		{
			Assert.That(await (reader.ReadAsync(CancellationToken.None)), "should return one record"); //Optional
			Assert.That(reader[0], Is.InstanceOf<DateTime>());
		}
	}
}

It seems that the glitch in a HasOptionalCancellationToken method: you are considering only generated methods, but not the ones which do exist before (Like ExecuteReaderAsync & ReadAsync)


EDIT: All "extra" CancellationToken.None are generated for ADO.NET code.

@hazzik
Copy link
Member Author

hazzik commented Aug 25, 2017

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

D:\Projects\nhibernate-core>ShowBuildMenu.bat
========================= NHIBERNATE BUILD MENU ==========================
--- TESTING ---
B. (Step 1) Set up a new test configuration for a particular database.
C. (Step 2) Activate a test configuration.
D. (Step 3) Run tests using active configuration.

--- BUILD ---
E. Build NHibernate (Debug)
F. Build NHibernate (Release)
G. Build Release Package (Also runs tests and creates documentation)

--- Code generation ---
H. Generate async code (Generates files in Async sub-folders)

--- TeamCity (CI) build options
I. TeamCity build menu

--- Exit ---
X. Make the beautiful build menu go away.

[B, C, D, E, F, G, H, I, X]?
h
NAnt 0.91 (Build 0.91.4312.0; release; 22/10/2011)
Copyright (C) 2001-2011 Gerry Shaw
http://nant.sourceforge.net

[loadtasks] Failure scanning "D:\Projects\nhibernate-core\Tools\nant\bin\NAnt.NUnit2Tasks.dll" for extensions. Unable to
 load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Buildfile: file:///D:/Projects/nhibernate-core/default.build
Target framework: Microsoft .NET Framework 4.0
Target(s) specified: generate-async


set-framework-configuration:


set-net-4.0-framework-configuration:


set-project-configuration:


set-debug-project-configuration:


common.init:


common.download-nuget:


common.nuget-restore:

     [exec] Feeds used:
     [exec]   C:\Users\contractor7\.nuget\packages\
     [exec]   https://api.nuget.org/v3/index.json
     [exec]   C:\Users\contractor7\.dotnet\NuGetFallbackFolder
     [exec]   C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\
     [exec]
     [exec] All packages listed in D:\Projects\nhibernate-core\Tools\packages.config are already installed.

common.solution-restore:

     [exec] Starting 'D:\Projects\nhibernate-core\Tools\msbuild.cmd (/t:Restore ./src/NHibernate.sln)' in 'D:\Projects\n
hibernate-core'
     [exec]
     [exec] D:\Projects\nhibernate-core>dotnet msbuild /t:Restore ./src/NHibernate.sln
     [exec] Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
     [exec] Copyright (C) Microsoft Corporation. All rights reserved.
     [exec]
     [exec]   Restore completed in 17.65 ms for D:\Projects\nhibernate-core\src\NHibernate.TestDatabaseSetup\NHibernate.
TestDatabaseSetup.csproj.
     [exec]   Restore completed in 17.98 ms for D:\Projects\nhibernate-core\src\NHibernate.Test\NHibernate.Test.csproj.
     [exec]   Restore completed in 17.62 ms for D:\Projects\nhibernate-core\src\NHibernate.DomainModel\NHibernate.Domain
Model.csproj.
     [exec]   Restore completed in 17.7 ms for D:\Projects\nhibernate-core\src\NHibernate\NHibernate.csproj.
     [exec]   Restore completed in 18 ms for D:\Projects\nhibernate-core\src\NHibernate.Test.VisualBasic\NHibernate.Test
.VisualBasic.vbproj.

common.find-async-generator-console:


common.get-nuget-package-path:


generate-async:

     [exec] AsyncGenerator
     [exec]
     [exec] 13:40:03,069 INFO  AsyncCodeGenerator:0 - Generating async code started
     [exec] 13:40:03,084 INFO  AsyncCodeGenerator:0 - Configuring solution 'D:\Projects\nhibernate-core\src\NHibernate.s
ln' prior analyzation started
     [exec] System.InvalidOperationException: One or more errors occurred while opening the solution:
     [exec] Msbuild failed when processing the file 'D:\Projects\nhibernate-core\src\NHibernate\NHibernate.csproj' with
message: The SDK 'Microsoft.NET.Sdk' specified could not be found.  D:\Projects\nhibernate-core\src\NHibernate\NHibernat
e.csproj
     [exec] Msbuild failed when processing the file 'D:\Projects\nhibernate-core\src\NHibernate.DomainModel\NHibernate.D
omainModel.csproj' with message: The SDK 'Microsoft.NET.Sdk' specified could not be found.  D:\Projects\nhibernate-core\
src\NHibernate.DomainModel\NHibernate.DomainModel.csproj
     [exec] Msbuild failed when processing the file 'D:\Projects\nhibernate-core\src\NHibernate.Test\NHibernate.Test.csp
roj' with message: The SDK 'Microsoft.NET.Sdk' specified could not be found.  D:\Projects\nhibernate-core\src\NHibernate
.Test\NHibernate.Test.csproj
     [exec] Msbuild failed when processing the file 'D:\Projects\nhibernate-core\src\NHibernate.TestDatabaseSetup\NHiber
nate.TestDatabaseSetup.csproj' with message: The SDK 'Microsoft.NET.Sdk' specified could not be found.  D:\Projects\nhib
ernate-core\src\NHibernate.TestDatabaseSetup\NHibernate.TestDatabaseSetup.csproj
     [exec] Cannot open project 'D:\Projects\nhibernate-core\src\NHibernate.Test.VisualBasic\NHibernate.Test.VisualBasic
.vbproj' because the language 'Visual Basic' is not supported.
     [exec] Hint: For suppressing errors use SuppressDiagnosticFaliures option.
     [exec]    at AsyncGenerator.AsyncCodeGenerator.<CreateSolutionData>d__5.MoveNext()
     [exec] --- End of stack trace from previous location where exception was thrown ---
     [exec]    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     [exec]    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
     [exec]    at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
     [exec]    at AsyncGenerator.AsyncCodeGenerator.<GenerateAsync>d__1.MoveNext()
     [exec] --- End of stack trace from previous location where exception was thrown ---
     [exec]    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
     [exec]    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
     [exec]    at AsyncGenerator.CommandLine.Program.Main(String[] args)

BUILD FAILED

D:\Projects\nhibernate-core\default.build(33,4):
External Program Failed: D:\Projects\nhibernate-core\Tools\CSharpAsyncGenerator.CommandLine.0.3.2\tools\AsyncGenerator.C
ommandLine.exe (return code was -1)

Total time: 13.5 seconds.

@fredericDelaporte
Copy link
Member

By the way @maca88, small typo it seems in log message: "use SuppressDiagnosticFaliures option".

@fredericDelaporte
Copy link
Member

@hazzik, that seems to be the new error detection added by maca88. There seems to be some misunderstanding here. @maca88, are you saying the new vbproj format cannot currently be supported by AsyncGenerator due to current tooling conditions? Should we exclude it somehow from AsyncGenerator processing?

@maca88
Copy link
Contributor

maca88 commented Aug 25, 2017

@fredericDelaporte will fix the typo, thanks
Visual basic projects are not supported by the generator in general, it doesn't matter if it is the new or the old csproj format. The error for the vb project was there all the time but the generator previously didn't throw it as the Diagnostics was never checked before.
With the SuppressDiagnosticFailures option, we can suppress the errors that are not relevant for us, like the error for the vb project as we never use it.
About those two other error will do some investigation, probably is something with the visual studio version. Will update to the newest VS 15.3.2 (currently 15.1) and install the .NET Core 2 in order to reproduce the problem and fix it.
Edit:

Should we exclude it somehow from AsyncGenerator processing?

As the OpenSolutionAsync does not have any overload to exclude some project from loading the only option is to load each project separately which the generator currently does not support it, so for now, the SuppressDiagnosticFailures can be used.

@maca88
Copy link
Contributor

maca88 commented Aug 25, 2017

The visual studio update to 15.3.2 caused the problem. I took the workaround from here until a proper solution will be available.

Edit: Now the generator supports loading each project separately so the SuppressDiagnosticFailures is not needed anymore in our case. Here is an example for the configuration by project (solution property is removed and each project have a filePath instead of name property).
Performance differences (tested on i7 860):

  • by solution loading -> 3:45 min
  • by projects loading -> 3:15 min

@fredericDelaporte
Copy link
Member

Thanks @maca88, good job.

A recent change will cause now a release build failure: warnings are no more accepted. But AsyncGenerator does generate in NHibernate.Test\Async\NHSpecificTest\DtcFailures\DtcFailuresFixture.cs an unused field, causing a warning in this class. This field is used in the sync counterpart. Either AsyncGenerator should be able to not generate it if it does not generate any method using it, or (probably more simple) it should disable such warnings in the files it generates.

@fredericDelaporte
Copy link
Member

It seems that the async generator needs to do two passes for taking into account new features (like the Linq DML in this case). The first pass add async to the feature, the second pass add it to the tests.
Maybe the tests generation should be done only after having already regenerated the NHibernate library. The current configuration seems to allow generating them in parallel.

@maca88
Copy link
Contributor

maca88 commented Aug 27, 2017

@fredericDelaporte, now the generator scans also fields usages and will not generate an unused field anymore.

It seems that the async generator needs to do two passes for taking into account new features

There was a bug when configuring projects separately.

Maybe the tests generation should be done only after having already regenerated the NHibernate library. The current configuration seems to allow generating them in parallel.

The code generation when having multiple projects is always sequential (before generating code for the second project the first must be completed), otherwise the end result may be different each time. The ConcurrentRun option applies only inside a processing step (analyze, scan, post-analyze and transform) that is responsable for the code generation of a particular project.

@fredericDelaporte
Copy link
Member

Great, now all builds should succeed.

@fredericDelaporte
Copy link
Member

I have done Alexander's suggested change on IFutureEnumerable<T>. This allows more tests to be generated by the way, once having hunted down all implicit IEnumerable<T> usages of IFutureEnumerable<T>. (They do not generates obsolescence warnings, I have spotted them by temporarily removing : IEnumerable<T> from IFutureEnumerable<T>.)

@@ -2442,7 +2442,7 @@ public static IFutureValue<TSource> ToFutureValue<TSource>(this IQueryable<TSour
{
var provider = GetNhProvider(source);
var future = provider.ExecuteFuture<TSource>(source.Expression);
return new FutureValue<TSource>(() => future, async cancellationToken => await future.GetEnumerableAsync(cancellationToken).ConfigureAwait(false));
return new FutureValue<TSource>(future.GetEnumerable, async cancellationToken => await future.GetEnumerableAsync(cancellationToken).ConfigureAwait(false));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for not simply writing

return new FutureValue<TSource>(future.GetEnumerable, future.GetEnumerableAsync);

instead for this line?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, I think before the return types were different (IList<T> -> IEnumerable<T>) so this was needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is there an IList<T>? Both are Task<IEnumerable<T>>. (The method argument return type and GetEnumerableAsync.) So if there is no other reasons, I simplify it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, IAsyncEnumerable<> was the reason why as we had an anonymous funtion where we called the ToList method to get an IEnumerable<>. Now this is not needed anymore so the simplification is the way to go.

@hazzik hazzik closed this Aug 29, 2017
@hazzik hazzik modified the milestone: 5.0 Aug 30, 2017
@hazzik hazzik deleted the AsyncGenerator branch September 1, 2017 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants