Skip to content
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

feat: typedef generation & type checking #261

Merged
merged 15 commits into from
Mar 9, 2021
Merged

feat: typedef generation & type checking #261

merged 15 commits into from
Mar 9, 2021

Conversation

Gozala
Copy link
Contributor

@Gozala Gozala commented Oct 14, 2020

This pull request makes following changes:

  1. Adds typedef generation
  2. Adds github action to run typecheck on changes & report missmatches in pull requests
  3. Adds / fixes bunch of jsdoc comments so it can both typecheck & generate

While making these changes I did discover certain things that are either bugs or I am misunderstanding what is going on there. I'll comment inline.

Please not that this change depends on:

  1. Adds TS typedefs so typed projects can inference pgte/moving-average#9
  2. fix: bitswap related typedefs js-ipfs#3580 (without this type check will fail)

@welcome
Copy link

welcome bot commented Oct 14, 2020

Thank you for submitting this PR!
A maintainer will be here shortly to review it.
We are super grateful, but we are also overloaded! Help us by making sure that:

  • The context for this PR is clear, with relevant discussion, decisions
    and stakeholders linked/mentioned.

  • Your contribution itself is clear (code comments, self-review for the
    rest) and in its best form. Follow the code contribution
    guidelines

    if they apply.

Getting other community members to do a review would be great help too on complex PRs (you can ask in the chats/forums). If you are unsure about something, just leave us a comment.
Next steps:

  • A maintainer will triage and assign priority to this PR, commenting on
    any missing things and potentially assigning a reviewer for high
    priority items.

  • The PR gets reviews, discussed and approvals as needed.

  • The PR is merged by maintainers when it has been approved and comments addressed.

We currently aim to provide initial feedback/triaging within two business days. Please keep an eye on any labelling actions, as these will indicate priorities and status of your contribution.
We are very grateful for your contribution!

@@ -112,7 +123,7 @@ class DecisionEngine {

// If there's nothing in the message, bail out
if (msg.empty) {
this._requestQueue.tasksDone(peerId, tasks)
peerId && this._requestQueue.tasksDone(peerId, tasks)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

peerId is obtained on line 76 via following code:

 const { peerId, tasks, pendingSize } = this._requestQueue.popTasks(this._opts.targetMessageSize)

However popTasks returns an object without peerId in 2 code paths out of 3. At the same time tasksDone and messageSent below do not expect undefined.

It was not exactly clear what made most sense here, so I just conditioned those calls on peerIds presence, but it would be good to make sure all this makes sense.

* @returns {boolean}
*/
isIdle () {
return this._pending.length === 0 && this._active.length === 0
return this._pending.length === 0 && this._active.size === 0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like there was a bug in the code, because _active is a Set instance which doesn't have length property.

_applyOp (op) {
const key = op[0]
const inc = op[1]

if (typeof inc !== 'number') {
throw new Error('invalid increment number:', inc)
throw new Error(`invalid increment number: ${inc}`)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seemed like a bug, so I just inlined incinto error message.

equals (other) {
if (this.full !== other.full ||
this.pendingBytes !== other.pendingBytes ||
!isMapEqual(this.wantlist, other.wantlist) ||
!isMapEqual(this.blocks, other.blocks) ||
// @TODO - Is this a bug ? `isMapEqual` only compare maps of blocks and
// wants
// @ts-ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one seems like a bug, but maybe it is not 🙄 So isMapEqual will compare only that keys match and will compare values if they are either Blocks or BitswapMessageEntrys (anything that has equals method on them actually). However blockPresences contain numbers so two would be equal even if the actual values are different, which doesn't seems like intended behavior.

P.S.: TS not happy mostly because it's told to expect maps of blocks or entries and is passed maps of numbers below.

constructor (selfPeerId, otherPeerId, network) {
this.peerId = otherPeerId
this.network = network
this.refcnt = 1

this._entries = []
this._log = logger(selfPeerId, 'msgqueue', otherPeerId.toB58String().slice(0, 8))
this._log = logger(selfPeerId, 'msgqueue')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what to do with the last argument here, because logger seems to take just two. I dropped it to retain the current behavior, but maybe concatenating last two would make more sense.

Copy link
Member

@hugomrdias hugomrdias left a comment

Choose a reason for hiding this comment

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

can we make an issue for the all ts-ignore's ?

.eslintrc Outdated Show resolved Hide resolved
.github/workflows/typecheck.yml Show resolved Hide resolved
package.json Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
src/stats/index.js Outdated Show resolved Hide resolved
src/stats/index.js Outdated Show resolved Hide resolved
src/stats/stat.js Outdated Show resolved Hide resolved
src/types.ts Outdated Show resolved Hide resolved
tsconfig.json Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
@Gozala
Copy link
Contributor Author

Gozala commented Nov 27, 2020

@hugomrdias I have addressed all of your feedback except:

  1. Migration from travis to github actions. I don't think it makes sense to bundle it with changes here & separate PR would make more sense IMO.
  2. AsyncIterable vs AsyncGenerator seems like a broader discussion to have.

@Gozala
Copy link
Contributor Author

Gozala commented Nov 27, 2020

Merging master introduced the problem with libp2p typedefs so temporarily this pull depends on libp2p/js-libp2p-interfaces#73

@Gozala
Copy link
Contributor Author

Gozala commented Nov 27, 2020

can we make an issue for the all ts-ignore's ?

#273

@Gozala
Copy link
Contributor Author

Gozala commented Nov 27, 2020

@hugomrdias any idea what is going on here ?
https://travis-ci.com/github/ipfs/js-ipfs-bitswap/jobs/449964224

For some reason typescript dependency does not get hoisted in the CI, while it does seem to get hoisted on my machine.

@hugomrdias
Copy link
Member

dunno maybe because some deps arent using aegir ts yet or the eslint config

package.json Outdated
"uuid": "^8.0.0"
"uuid": "^8.0.0",
"@types/debug": "^4.1.5",
"typescript": "^4.0.5"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"typescript": "^4.0.5"

package.json Outdated
"files": [
"dist",
"src"
],
"scripts": {
"prepare": "aegir build",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need full build here

Suggested change
"prepare": "aegir build",
"prepare": "aegir ts -p check",

cid,
{
// TODO: Should this be a timeout options insetad ?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vasco-santos do you know if we should be passing timeout instead of maxTimeout

Copy link
Member

Choose a reason for hiding this comment

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

This should be timeout - the option is passed through to whatever contenting routing modules are configured - these are either libp2p-delegated-content-routing or libp2p-kad-dht and neither of those support a maxTimeout option, only timeout.

* @returns {Promise<Connection>}
*/
async connectTo (peer, options) { // eslint-disable-line require-await
if (!this._running) {
throw new Error('network isn\'t running')
}

return this.libp2p.dial(peer, options)
// TODO: Figure out inconsistency here.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vasco-santos could you please help me out understand what is expected here. Passed peer here comes from libp2p.contentRouting.findProviders which contains both peer id and multiaddr, but then libp2p.dial expects either peerId or multiaddr and attempts to turn it back into Provider.

I do not know all the ins and outs here, but clearly something makes wrong assumptions.

Copy link
Member

Choose a reason for hiding this comment

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

libp2p.contentRouting.findProviders should return only PeerIDs.

The idea is that libp2p manages the actual multiaddrs internally and we don't worry about them at this level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created an issue in libp2p libp2p/js-libp2p#899

equals (other) {
if (this.full !== other.full ||
this.pendingBytes !== other.pendingBytes ||
!isMapEqual(this.wantlist, other.wantlist) ||
!isMapEqual(this.blocks, other.blocks) ||
// @TODO - Is this a bug ?
// @ts-expect-error - isMap equals map values to be objects not numbers
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hugomrdias This looks like a bug to me, could you please confirm or tell me if I'm not seeing something ?

} else {
this.wantlist.remove(e.cid)
}
} else {
this._log('adding to wl')
// TODO: Figure out the wantType
// @ts-expect-error - requires wantType
this.wantlist.add(e.cid, e.priority)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hugomrdias do you understand why wantType is omitted here ? Following lines make make me believe it should be provided, but I'm not sure what the value should be in this instance

if (entry.wantType === Message.WantType.Have && wantType === Message.WantType.Block) {
entry.wantType = wantType
}
} else {
this.set.set(cidStr, new Entry(cid, priority, wantType))

Copy link
Member

Choose a reason for hiding this comment

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

I think the WantType ends up defaulting to Block if it's omitted. The Have/Block distinction is a later optimisation, I think maybe it just got missed off here when it was implemented.

@@ -72,9 +97,16 @@ class Wantlist {
}

sortedEntries () {
// TODO: Figure out if this is an actual bug.
// @ts-expect-error - Property 'key' does not exist on type 'WantListEntry'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@achingbrain do you have an idea what this meant to be, I'm guessing key used to be what cid is today, but I'm not sure. Nor I have a clue how this supposed to be sorted.

Copy link
Member

Choose a reason for hiding this comment

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

Looks like a bug. I think this is just supposed to be a list of wantlist entries sorted by string CID, rather than insertion order which would be the default - the .key property access is probably due to misreading the docs on the output of Map.entries()?

I'm not sure what the value of it being sorted is, other than the output being stable - it's probably better as a set TBH.

Something we should create an issue for and maybe address later.

Copy link
Member

@achingbrain achingbrain left a comment

Choose a reason for hiding this comment

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

LGTM - a few bugs and inconsistencies are well pointed out but we should get this in and address those as follow ups.

Just needs the github URLs removing from deps and it's good to go.

package.json Outdated Show resolved Hide resolved
src/index.js Show resolved Hide resolved
cid,
{
// TODO: Should this be a timeout options insetad ?
Copy link
Member

Choose a reason for hiding this comment

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

This should be timeout - the option is passed through to whatever contenting routing modules are configured - these are either libp2p-delegated-content-routing or libp2p-kad-dht and neither of those support a maxTimeout option, only timeout.

* @param {Object} options
* @param {AbortSignal} options.abortSignal
* @returns {void}
* @param {Object} [options]
Copy link
Member

Choose a reason for hiding this comment

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

Small nit and not a blocker but in this PR I see @param {Object} in some places and @param {object} in others.

I know it makes no difference functionally, but we should be consistent (and it should really be enforced by our linting rules). I think we're using @param {object} everywhere else.

* @returns {Promise<Connection>}
*/
async connectTo (peer, options) { // eslint-disable-line require-await
if (!this._running) {
throw new Error('network isn\'t running')
}

return this.libp2p.dial(peer, options)
// TODO: Figure out inconsistency here.
Copy link
Member

Choose a reason for hiding this comment

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

libp2p.contentRouting.findProviders should return only PeerIDs.

The idea is that libp2p manages the actual multiaddrs internally and we don't worry about them at this level.

@@ -72,9 +97,16 @@ class Wantlist {
}

sortedEntries () {
// TODO: Figure out if this is an actual bug.
// @ts-expect-error - Property 'key' does not exist on type 'WantListEntry'
Copy link
Member

Choose a reason for hiding this comment

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

Looks like a bug. I think this is just supposed to be a list of wantlist entries sorted by string CID, rather than insertion order which would be the default - the .key property access is probably due to misreading the docs on the output of Map.entries()?

I'm not sure what the value of it being sorted is, other than the output being stable - it's probably better as a set TBH.

Something we should create an issue for and maybe address later.

} else {
this.wantlist.remove(e.cid)
}
} else {
this._log('adding to wl')
// TODO: Figure out the wantType
// @ts-expect-error - requires wantType
this.wantlist.add(e.cid, e.priority)
Copy link
Member

Choose a reason for hiding this comment

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

I think the WantType ends up defaulting to Block if it's omitted. The Have/Block distinction is a later optimisation, I think maybe it just got missed off here when it was implemented.

achingbrain added a commit to ipfs/js-ipfs that referenced this pull request Mar 8, 2021
Fixes up types so they align here ipfs/js-ipfs-bitswap#261

Co-authored-by: achingbrain <[email protected]>
@achingbrain achingbrain merged commit fca78c8 into master Mar 9, 2021
@achingbrain achingbrain deleted the typegen branch March 9, 2021 12:13
@Gozala
Copy link
Contributor Author

Gozala commented Mar 10, 2021

Created an issue for all the discovered inconsistencies #303

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants